Merge branch 'v4.8.x'
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         int oldFMM = forwardMostMove;
4167                         gotPremove = 0;
4168                         ClearPremoveHighlights();
4169                         if (appData.debugMode)
4170                           fprintf(debugFP, "Sending premove:\n");
4171                           UserMoveEvent(premoveFromX, premoveFromY,
4172                                         premoveToX, premoveToY,
4173                                         premovePromoChar);
4174                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4175                           if(moveList[oldFMM-1][1] != '@')
4176                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4177                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4178                           else // (drop)
4179                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4180                         }
4181                       }
4182                     }
4183
4184                     /* Usually suppress following prompt */
4185                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4186                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4187                         if (looking_at(buf, &i, "*% ")) {
4188                             savingComment = FALSE;
4189                             suppressKibitz = 0;
4190                         }
4191                     }
4192                     next_out = i;
4193                 } else if (started == STARTED_HOLDINGS) {
4194                     int gamenum;
4195                     char new_piece[MSG_SIZ];
4196                     started = STARTED_NONE;
4197                     parse[parse_pos] = NULLCHAR;
4198                     if (appData.debugMode)
4199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4200                                                         parse, currentMove);
4201                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4202                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4203                         if (gameInfo.variant == VariantNormal) {
4204                           /* [HGM] We seem to switch variant during a game!
4205                            * Presumably no holdings were displayed, so we have
4206                            * to move the position two files to the right to
4207                            * create room for them!
4208                            */
4209                           VariantClass newVariant;
4210                           switch(gameInfo.boardWidth) { // base guess on board width
4211                                 case 9:  newVariant = VariantShogi; break;
4212                                 case 10: newVariant = VariantGreat; break;
4213                                 default: newVariant = VariantCrazyhouse; break;
4214                           }
4215                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4216                           /* Get a move list just to see the header, which
4217                              will tell us whether this is really bug or zh */
4218                           if (ics_getting_history == H_FALSE) {
4219                             ics_getting_history = H_REQUESTED;
4220                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4221                             SendToICS(str);
4222                           }
4223                         }
4224                         new_piece[0] = NULLCHAR;
4225                         sscanf(parse, "game %d white [%s black [%s <- %s",
4226                                &gamenum, white_holding, black_holding,
4227                                new_piece);
4228                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4229                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4230                         /* [HGM] copy holdings to board holdings area */
4231                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4232                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4233                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4234 #if ZIPPY
4235                         if (appData.zippyPlay && first.initDone) {
4236                             ZippyHoldings(white_holding, black_holding,
4237                                           new_piece);
4238                         }
4239 #endif /*ZIPPY*/
4240                         if (tinyLayout || smallLayout) {
4241                             char wh[16], bh[16];
4242                             PackHolding(wh, white_holding);
4243                             PackHolding(bh, black_holding);
4244                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4245                                     gameInfo.white, gameInfo.black);
4246                         } else {
4247                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4248                                     gameInfo.white, white_holding, _("vs."),
4249                                     gameInfo.black, black_holding);
4250                         }
4251                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4252                         DrawPosition(FALSE, boards[currentMove]);
4253                         DisplayTitle(str);
4254                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4255                         sscanf(parse, "game %d white [%s black [%s <- %s",
4256                                &gamenum, white_holding, black_holding,
4257                                new_piece);
4258                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4259                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4260                         /* [HGM] copy holdings to partner-board holdings area */
4261                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4262                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4263                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4264                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4265                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4266                       }
4267                     }
4268                     /* Suppress following prompt */
4269                     if (looking_at(buf, &i, "*% ")) {
4270                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4271                         savingComment = FALSE;
4272                         suppressKibitz = 0;
4273                     }
4274                     next_out = i;
4275                 }
4276                 continue;
4277             }
4278
4279             i++;                /* skip unparsed character and loop back */
4280         }
4281
4282         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4283 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4284 //          SendToPlayer(&buf[next_out], i - next_out);
4285             started != STARTED_HOLDINGS && leftover_start > next_out) {
4286             SendToPlayer(&buf[next_out], leftover_start - next_out);
4287             next_out = i;
4288         }
4289
4290         leftover_len = buf_len - leftover_start;
4291         /* if buffer ends with something we couldn't parse,
4292            reparse it after appending the next read */
4293
4294     } else if (count == 0) {
4295         RemoveInputSource(isr);
4296         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4297     } else {
4298         DisplayFatalError(_("Error reading from ICS"), error, 1);
4299     }
4300 }
4301
4302
4303 /* Board style 12 looks like this:
4304
4305    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4306
4307  * The "<12> " is stripped before it gets to this routine.  The two
4308  * trailing 0's (flip state and clock ticking) are later addition, and
4309  * some chess servers may not have them, or may have only the first.
4310  * Additional trailing fields may be added in the future.
4311  */
4312
4313 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4314
4315 #define RELATION_OBSERVING_PLAYED    0
4316 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4317 #define RELATION_PLAYING_MYMOVE      1
4318 #define RELATION_PLAYING_NOTMYMOVE  -1
4319 #define RELATION_EXAMINING           2
4320 #define RELATION_ISOLATED_BOARD     -3
4321 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4322
4323 void
4324 ParseBoard12 (char *string)
4325 {
4326 #if ZIPPY
4327     int i, takeback;
4328     char *bookHit = NULL; // [HGM] book
4329 #endif
4330     GameMode newGameMode;
4331     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4332     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4333     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4334     char to_play, board_chars[200];
4335     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4336     char black[32], white[32];
4337     Board board;
4338     int prevMove = currentMove;
4339     int ticking = 2;
4340     ChessMove moveType;
4341     int fromX, fromY, toX, toY;
4342     char promoChar;
4343     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4344     Boolean weird = FALSE, reqFlag = FALSE;
4345
4346     fromX = fromY = toX = toY = -1;
4347
4348     newGame = FALSE;
4349
4350     if (appData.debugMode)
4351       fprintf(debugFP, "Parsing board: %s\n", string);
4352
4353     move_str[0] = NULLCHAR;
4354     elapsed_time[0] = NULLCHAR;
4355     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4356         int  i = 0, j;
4357         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4358             if(string[i] == ' ') { ranks++; files = 0; }
4359             else files++;
4360             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4361             i++;
4362         }
4363         for(j = 0; j <i; j++) board_chars[j] = string[j];
4364         board_chars[i] = '\0';
4365         string += i + 1;
4366     }
4367     n = sscanf(string, PATTERN, &to_play, &double_push,
4368                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4369                &gamenum, white, black, &relation, &basetime, &increment,
4370                &white_stren, &black_stren, &white_time, &black_time,
4371                &moveNum, str, elapsed_time, move_str, &ics_flip,
4372                &ticking);
4373
4374     if (n < 21) {
4375         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4376         DisplayError(str, 0);
4377         return;
4378     }
4379
4380     /* Convert the move number to internal form */
4381     moveNum = (moveNum - 1) * 2;
4382     if (to_play == 'B') moveNum++;
4383     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4385                         0, 1);
4386       return;
4387     }
4388
4389     switch (relation) {
4390       case RELATION_OBSERVING_PLAYED:
4391       case RELATION_OBSERVING_STATIC:
4392         if (gamenum == -1) {
4393             /* Old ICC buglet */
4394             relation = RELATION_OBSERVING_STATIC;
4395         }
4396         newGameMode = IcsObserving;
4397         break;
4398       case RELATION_PLAYING_MYMOVE:
4399       case RELATION_PLAYING_NOTMYMOVE:
4400         newGameMode =
4401           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4402             IcsPlayingWhite : IcsPlayingBlack;
4403         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4404         break;
4405       case RELATION_EXAMINING:
4406         newGameMode = IcsExamining;
4407         break;
4408       case RELATION_ISOLATED_BOARD:
4409       default:
4410         /* Just display this board.  If user was doing something else,
4411            we will forget about it until the next board comes. */
4412         newGameMode = IcsIdle;
4413         break;
4414       case RELATION_STARTING_POSITION:
4415         newGameMode = gameMode;
4416         break;
4417     }
4418
4419     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4420         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4421          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4422       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4423       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4424       static int lastBgGame = -1;
4425       char *toSqr;
4426       for (k = 0; k < ranks; k++) {
4427         for (j = 0; j < files; j++)
4428           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429         if(gameInfo.holdingsWidth > 1) {
4430              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432         }
4433       }
4434       CopyBoard(partnerBoard, board);
4435       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4436         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4437         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4438       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4439       if(toSqr = strchr(str, '-')) {
4440         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4441         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4442       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4443       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4444       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4445       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4446       if(twoBoards) {
4447           DisplayWhiteClock(white_time*fac, to_play == 'W');
4448           DisplayBlackClock(black_time*fac, to_play != 'W');
4449           activePartner = to_play;
4450           if(gamenum != lastBgGame) {
4451               char buf[MSG_SIZ];
4452               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4453               DisplayTitle(buf);
4454           }
4455           lastBgGame = gamenum;
4456           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4457                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4458       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4459                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4460       if(!twoBoards) DisplayMessage(partnerStatus, "");
4461         partnerBoardValid = TRUE;
4462       return;
4463     }
4464
4465     if(appData.dualBoard && appData.bgObserve) {
4466         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4467             SendToICS(ics_prefix), SendToICS("pobserve\n");
4468         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4469             char buf[MSG_SIZ];
4470             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4471             SendToICS(buf);
4472         }
4473     }
4474
4475     /* Modify behavior for initial board display on move listing
4476        of wild games.
4477        */
4478     switch (ics_getting_history) {
4479       case H_FALSE:
4480       case H_REQUESTED:
4481         break;
4482       case H_GOT_REQ_HEADER:
4483       case H_GOT_UNREQ_HEADER:
4484         /* This is the initial position of the current game */
4485         gamenum = ics_gamenum;
4486         moveNum = 0;            /* old ICS bug workaround */
4487         if (to_play == 'B') {
4488           startedFromSetupPosition = TRUE;
4489           blackPlaysFirst = TRUE;
4490           moveNum = 1;
4491           if (forwardMostMove == 0) forwardMostMove = 1;
4492           if (backwardMostMove == 0) backwardMostMove = 1;
4493           if (currentMove == 0) currentMove = 1;
4494         }
4495         newGameMode = gameMode;
4496         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4497         break;
4498       case H_GOT_UNWANTED_HEADER:
4499         /* This is an initial board that we don't want */
4500         return;
4501       case H_GETTING_MOVES:
4502         /* Should not happen */
4503         DisplayError(_("Error gathering move list: extra board"), 0);
4504         ics_getting_history = H_FALSE;
4505         return;
4506     }
4507
4508    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4509                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4510                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4511      /* [HGM] We seem to have switched variant unexpectedly
4512       * Try to guess new variant from board size
4513       */
4514           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4515           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4516           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4517           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4518           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4519           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4520           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4521           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4522           /* Get a move list just to see the header, which
4523              will tell us whether this is really bug or zh */
4524           if (ics_getting_history == H_FALSE) {
4525             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528           }
4529     }
4530
4531     /* Take action if this is the first board of a new game, or of a
4532        different game than is currently being displayed.  */
4533     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4534         relation == RELATION_ISOLATED_BOARD) {
4535
4536         /* Forget the old game and get the history (if any) of the new one */
4537         if (gameMode != BeginningOfGame) {
4538           Reset(TRUE, TRUE);
4539         }
4540         newGame = TRUE;
4541         if (appData.autoRaiseBoard) BoardToTop();
4542         prevMove = -3;
4543         if (gamenum == -1) {
4544             newGameMode = IcsIdle;
4545         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4546                    appData.getMoveList && !reqFlag) {
4547             /* Need to get game history */
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552
4553         /* Initially flip the board to have black on the bottom if playing
4554            black or if the ICS flip flag is set, but let the user change
4555            it with the Flip View button. */
4556         flipView = appData.autoFlipView ?
4557           (newGameMode == IcsPlayingBlack) || ics_flip :
4558           appData.flipView;
4559
4560         /* Done with values from previous mode; copy in new ones */
4561         gameMode = newGameMode;
4562         ModeHighlight();
4563         ics_gamenum = gamenum;
4564         if (gamenum == gs_gamenum) {
4565             int klen = strlen(gs_kind);
4566             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4567             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4568             gameInfo.event = StrSave(str);
4569         } else {
4570             gameInfo.event = StrSave("ICS game");
4571         }
4572         gameInfo.site = StrSave(appData.icsHost);
4573         gameInfo.date = PGNDate();
4574         gameInfo.round = StrSave("-");
4575         gameInfo.white = StrSave(white);
4576         gameInfo.black = StrSave(black);
4577         timeControl = basetime * 60 * 1000;
4578         timeControl_2 = 0;
4579         timeIncrement = increment * 1000;
4580         movesPerSession = 0;
4581         gameInfo.timeControl = TimeControlTagValue();
4582         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4583   if (appData.debugMode) {
4584     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4585     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4586     setbuf(debugFP, NULL);
4587   }
4588
4589         gameInfo.outOfBook = NULL;
4590
4591         /* Do we have the ratings? */
4592         if (strcmp(player1Name, white) == 0 &&
4593             strcmp(player2Name, black) == 0) {
4594             if (appData.debugMode)
4595               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4596                       player1Rating, player2Rating);
4597             gameInfo.whiteRating = player1Rating;
4598             gameInfo.blackRating = player2Rating;
4599         } else if (strcmp(player2Name, white) == 0 &&
4600                    strcmp(player1Name, black) == 0) {
4601             if (appData.debugMode)
4602               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4603                       player2Rating, player1Rating);
4604             gameInfo.whiteRating = player2Rating;
4605             gameInfo.blackRating = player1Rating;
4606         }
4607         player1Name[0] = player2Name[0] = NULLCHAR;
4608
4609         /* Silence shouts if requested */
4610         if (appData.quietPlay &&
4611             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4612             SendToICS(ics_prefix);
4613             SendToICS("set shout 0\n");
4614         }
4615     }
4616
4617     /* Deal with midgame name changes */
4618     if (!newGame) {
4619         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4620             if (gameInfo.white) free(gameInfo.white);
4621             gameInfo.white = StrSave(white);
4622         }
4623         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4624             if (gameInfo.black) free(gameInfo.black);
4625             gameInfo.black = StrSave(black);
4626         }
4627     }
4628
4629     /* Throw away game result if anything actually changes in examine mode */
4630     if (gameMode == IcsExamining && !newGame) {
4631         gameInfo.result = GameUnfinished;
4632         if (gameInfo.resultDetails != NULL) {
4633             free(gameInfo.resultDetails);
4634             gameInfo.resultDetails = NULL;
4635         }
4636     }
4637
4638     /* In pausing && IcsExamining mode, we ignore boards coming
4639        in if they are in a different variation than we are. */
4640     if (pauseExamInvalid) return;
4641     if (pausing && gameMode == IcsExamining) {
4642         if (moveNum <= pauseExamForwardMostMove) {
4643             pauseExamInvalid = TRUE;
4644             forwardMostMove = pauseExamForwardMostMove;
4645             return;
4646         }
4647     }
4648
4649   if (appData.debugMode) {
4650     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4651   }
4652     /* Parse the board */
4653     for (k = 0; k < ranks; k++) {
4654       for (j = 0; j < files; j++)
4655         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4656       if(gameInfo.holdingsWidth > 1) {
4657            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4658            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4659       }
4660     }
4661     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4662       board[5][BOARD_RGHT+1] = WhiteAngel;
4663       board[6][BOARD_RGHT+1] = WhiteMarshall;
4664       board[1][0] = BlackMarshall;
4665       board[2][0] = BlackAngel;
4666       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4667     }
4668     CopyBoard(boards[moveNum], board);
4669     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4670     if (moveNum == 0) {
4671         startedFromSetupPosition =
4672           !CompareBoards(board, initialPosition);
4673         if(startedFromSetupPosition)
4674             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4675     }
4676
4677     /* [HGM] Set castling rights. Take the outermost Rooks,
4678        to make it also work for FRC opening positions. Note that board12
4679        is really defective for later FRC positions, as it has no way to
4680        indicate which Rook can castle if they are on the same side of King.
4681        For the initial position we grant rights to the outermost Rooks,
4682        and remember thos rights, and we then copy them on positions
4683        later in an FRC game. This means WB might not recognize castlings with
4684        Rooks that have moved back to their original position as illegal,
4685        but in ICS mode that is not its job anyway.
4686     */
4687     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4688     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4689
4690         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4691             if(board[0][i] == WhiteRook) j = i;
4692         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4694             if(board[0][i] == WhiteRook) j = i;
4695         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4696         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4697             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4698         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4699         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4700             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4701         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702
4703         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4704         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4705         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4706             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4707         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4708             if(board[BOARD_HEIGHT-1][k] == bKing)
4709                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4710         if(gameInfo.variant == VariantTwoKings) {
4711             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4712             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4713             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4714         }
4715     } else { int r;
4716         r = boards[moveNum][CASTLING][0] = initialRights[0];
4717         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4718         r = boards[moveNum][CASTLING][1] = initialRights[1];
4719         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4720         r = boards[moveNum][CASTLING][3] = initialRights[3];
4721         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4722         r = boards[moveNum][CASTLING][4] = initialRights[4];
4723         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4724         /* wildcastle kludge: always assume King has rights */
4725         r = boards[moveNum][CASTLING][2] = initialRights[2];
4726         r = boards[moveNum][CASTLING][5] = initialRights[5];
4727     }
4728     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4729     boards[moveNum][EP_STATUS] = EP_NONE;
4730     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4731     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4732     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4733
4734
4735     if (ics_getting_history == H_GOT_REQ_HEADER ||
4736         ics_getting_history == H_GOT_UNREQ_HEADER) {
4737         /* This was an initial position from a move list, not
4738            the current position */
4739         return;
4740     }
4741
4742     /* Update currentMove and known move number limits */
4743     newMove = newGame || moveNum > forwardMostMove;
4744
4745     if (newGame) {
4746         forwardMostMove = backwardMostMove = currentMove = moveNum;
4747         if (gameMode == IcsExamining && moveNum == 0) {
4748           /* Workaround for ICS limitation: we are not told the wild
4749              type when starting to examine a game.  But if we ask for
4750              the move list, the move list header will tell us */
4751             ics_getting_history = H_REQUESTED;
4752             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4753             SendToICS(str);
4754         }
4755     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4756                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4757 #if ZIPPY
4758         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4759         /* [HGM] applied this also to an engine that is silently watching        */
4760         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4761             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4762             gameInfo.variant == currentlyInitializedVariant) {
4763           takeback = forwardMostMove - moveNum;
4764           for (i = 0; i < takeback; i++) {
4765             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4766             SendToProgram("undo\n", &first);
4767           }
4768         }
4769 #endif
4770
4771         forwardMostMove = moveNum;
4772         if (!pausing || currentMove > forwardMostMove)
4773           currentMove = forwardMostMove;
4774     } else {
4775         /* New part of history that is not contiguous with old part */
4776         if (pausing && gameMode == IcsExamining) {
4777             pauseExamInvalid = TRUE;
4778             forwardMostMove = pauseExamForwardMostMove;
4779             return;
4780         }
4781         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4782 #if ZIPPY
4783             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4784                 // [HGM] when we will receive the move list we now request, it will be
4785                 // fed to the engine from the first move on. So if the engine is not
4786                 // in the initial position now, bring it there.
4787                 InitChessProgram(&first, 0);
4788             }
4789 #endif
4790             ics_getting_history = H_REQUESTED;
4791             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4792             SendToICS(str);
4793         }
4794         forwardMostMove = backwardMostMove = currentMove = moveNum;
4795     }
4796
4797     /* Update the clocks */
4798     if (strchr(elapsed_time, '.')) {
4799       /* Time is in ms */
4800       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4801       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4802     } else {
4803       /* Time is in seconds */
4804       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4805       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4806     }
4807
4808
4809 #if ZIPPY
4810     if (appData.zippyPlay && newGame &&
4811         gameMode != IcsObserving && gameMode != IcsIdle &&
4812         gameMode != IcsExamining)
4813       ZippyFirstBoard(moveNum, basetime, increment);
4814 #endif
4815
4816     /* Put the move on the move list, first converting
4817        to canonical algebraic form. */
4818     if (moveNum > 0) {
4819   if (appData.debugMode) {
4820     int f = forwardMostMove;
4821     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4822             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4823             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4824     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4825     fprintf(debugFP, "moveNum = %d\n", moveNum);
4826     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4827     setbuf(debugFP, NULL);
4828   }
4829         if (moveNum <= backwardMostMove) {
4830             /* We don't know what the board looked like before
4831                this move.  Punt. */
4832           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4833             strcat(parseList[moveNum - 1], " ");
4834             strcat(parseList[moveNum - 1], elapsed_time);
4835             moveList[moveNum - 1][0] = NULLCHAR;
4836         } else if (strcmp(move_str, "none") == 0) {
4837             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4838             /* Again, we don't know what the board looked like;
4839                this is really the start of the game. */
4840             parseList[moveNum - 1][0] = NULLCHAR;
4841             moveList[moveNum - 1][0] = NULLCHAR;
4842             backwardMostMove = moveNum;
4843             startedFromSetupPosition = TRUE;
4844             fromX = fromY = toX = toY = -1;
4845         } else {
4846           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4847           //                 So we parse the long-algebraic move string in stead of the SAN move
4848           int valid; char buf[MSG_SIZ], *prom;
4849
4850           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4851                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4852           // str looks something like "Q/a1-a2"; kill the slash
4853           if(str[1] == '/')
4854             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4855           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4856           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4857                 strcat(buf, prom); // long move lacks promo specification!
4858           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4859                 if(appData.debugMode)
4860                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4861                 safeStrCpy(move_str, buf, MSG_SIZ);
4862           }
4863           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4864                                 &fromX, &fromY, &toX, &toY, &promoChar)
4865                || ParseOneMove(buf, moveNum - 1, &moveType,
4866                                 &fromX, &fromY, &toX, &toY, &promoChar);
4867           // end of long SAN patch
4868           if (valid) {
4869             (void) CoordsToAlgebraic(boards[moveNum - 1],
4870                                      PosFlags(moveNum - 1),
4871                                      fromY, fromX, toY, toX, promoChar,
4872                                      parseList[moveNum-1]);
4873             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4874               case MT_NONE:
4875               case MT_STALEMATE:
4876               default:
4877                 break;
4878               case MT_CHECK:
4879                 if(!IS_SHOGI(gameInfo.variant))
4880                     strcat(parseList[moveNum - 1], "+");
4881                 break;
4882               case MT_CHECKMATE:
4883               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4884                 strcat(parseList[moveNum - 1], "#");
4885                 break;
4886             }
4887             strcat(parseList[moveNum - 1], " ");
4888             strcat(parseList[moveNum - 1], elapsed_time);
4889             /* currentMoveString is set as a side-effect of ParseOneMove */
4890             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4891             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4892             strcat(moveList[moveNum - 1], "\n");
4893
4894             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4895                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4896               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4897                 ChessSquare old, new = boards[moveNum][k][j];
4898                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4899                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4900                   if(old == new) continue;
4901                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4902                   else if(new == WhiteWazir || new == BlackWazir) {
4903                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4904                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4905                       else boards[moveNum][k][j] = old; // preserve type of Gold
4906                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4907                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4908               }
4909           } else {
4910             /* Move from ICS was illegal!?  Punt. */
4911             if (appData.debugMode) {
4912               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4913               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4914             }
4915             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4916             strcat(parseList[moveNum - 1], " ");
4917             strcat(parseList[moveNum - 1], elapsed_time);
4918             moveList[moveNum - 1][0] = NULLCHAR;
4919             fromX = fromY = toX = toY = -1;
4920           }
4921         }
4922   if (appData.debugMode) {
4923     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4924     setbuf(debugFP, NULL);
4925   }
4926
4927 #if ZIPPY
4928         /* Send move to chess program (BEFORE animating it). */
4929         if (appData.zippyPlay && !newGame && newMove &&
4930            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4931
4932             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4933                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4934                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4935                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4936                             move_str);
4937                     DisplayError(str, 0);
4938                 } else {
4939                     if (first.sendTime) {
4940                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4941                     }
4942                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4943                     if (firstMove && !bookHit) {
4944                         firstMove = FALSE;
4945                         if (first.useColors) {
4946                           SendToProgram(gameMode == IcsPlayingWhite ?
4947                                         "white\ngo\n" :
4948                                         "black\ngo\n", &first);
4949                         } else {
4950                           SendToProgram("go\n", &first);
4951                         }
4952                         first.maybeThinking = TRUE;
4953                     }
4954                 }
4955             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4956               if (moveList[moveNum - 1][0] == NULLCHAR) {
4957                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4958                 DisplayError(str, 0);
4959               } else {
4960                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4961                 SendMoveToProgram(moveNum - 1, &first);
4962               }
4963             }
4964         }
4965 #endif
4966     }
4967
4968     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4969         /* If move comes from a remote source, animate it.  If it
4970            isn't remote, it will have already been animated. */
4971         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4972             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4973         }
4974         if (!pausing && appData.highlightLastMove) {
4975             SetHighlights(fromX, fromY, toX, toY);
4976         }
4977     }
4978
4979     /* Start the clocks */
4980     whiteFlag = blackFlag = FALSE;
4981     appData.clockMode = !(basetime == 0 && increment == 0);
4982     if (ticking == 0) {
4983       ics_clock_paused = TRUE;
4984       StopClocks();
4985     } else if (ticking == 1) {
4986       ics_clock_paused = FALSE;
4987     }
4988     if (gameMode == IcsIdle ||
4989         relation == RELATION_OBSERVING_STATIC ||
4990         relation == RELATION_EXAMINING ||
4991         ics_clock_paused)
4992       DisplayBothClocks();
4993     else
4994       StartClocks();
4995
4996     /* Display opponents and material strengths */
4997     if (gameInfo.variant != VariantBughouse &&
4998         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4999         if (tinyLayout || smallLayout) {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5002                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5006                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5007                     basetime, increment, (int) gameInfo.variant);
5008         } else {
5009             if(gameInfo.variant == VariantNormal)
5010               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5011                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5012                     basetime, increment);
5013             else
5014               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5015                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5016                     basetime, increment, VariantName(gameInfo.variant));
5017         }
5018         DisplayTitle(str);
5019   if (appData.debugMode) {
5020     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5021   }
5022     }
5023
5024
5025     /* Display the board */
5026     if (!pausing && !appData.noGUI) {
5027
5028       if (appData.premove)
5029           if (!gotPremove ||
5030              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5031              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5032               ClearPremoveHighlights();
5033
5034       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5035         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5036       DrawPosition(j, boards[currentMove]);
5037
5038       DisplayMove(moveNum - 1);
5039       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5040             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5041               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5042         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5043       }
5044     }
5045
5046     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5047 #if ZIPPY
5048     if(bookHit) { // [HGM] book: simulate book reply
5049         static char bookMove[MSG_SIZ]; // a bit generous?
5050
5051         programStats.nodes = programStats.depth = programStats.time =
5052         programStats.score = programStats.got_only_move = 0;
5053         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5054
5055         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5056         strcat(bookMove, bookHit);
5057         HandleMachineMove(bookMove, &first);
5058     }
5059 #endif
5060 }
5061
5062 void
5063 GetMoveListEvent ()
5064 {
5065     char buf[MSG_SIZ];
5066     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5067         ics_getting_history = H_REQUESTED;
5068         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5069         SendToICS(buf);
5070     }
5071 }
5072
5073 void
5074 SendToBoth (char *msg)
5075 {   // to make it easy to keep two engines in step in dual analysis
5076     SendToProgram(msg, &first);
5077     if(second.analyzing) SendToProgram(msg, &second);
5078 }
5079
5080 void
5081 AnalysisPeriodicEvent (int force)
5082 {
5083     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5084          && !force) || !appData.periodicUpdates)
5085       return;
5086
5087     /* Send . command to Crafty to collect stats */
5088     SendToBoth(".\n");
5089
5090     /* Don't send another until we get a response (this makes
5091        us stop sending to old Crafty's which don't understand
5092        the "." command (sending illegal cmds resets node count & time,
5093        which looks bad)) */
5094     programStats.ok_to_send = 0;
5095 }
5096
5097 void
5098 ics_update_width (int new_width)
5099 {
5100         ics_printf("set width %d\n", new_width);
5101 }
5102
5103 void
5104 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5105 {
5106     char buf[MSG_SIZ];
5107
5108     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5109         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5110             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5111             SendToProgram(buf, cps);
5112             return;
5113         }
5114         // null move in variant where engine does not understand it (for analysis purposes)
5115         SendBoard(cps, moveNum + 1); // send position after move in stead.
5116         return;
5117     }
5118     if (cps->useUsermove) {
5119       SendToProgram("usermove ", cps);
5120     }
5121     if (cps->useSAN) {
5122       char *space;
5123       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5124         int len = space - parseList[moveNum];
5125         memcpy(buf, parseList[moveNum], len);
5126         buf[len++] = '\n';
5127         buf[len] = NULLCHAR;
5128       } else {
5129         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5130       }
5131       SendToProgram(buf, cps);
5132     } else {
5133       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5134         AlphaRank(moveList[moveNum], 4);
5135         SendToProgram(moveList[moveNum], cps);
5136         AlphaRank(moveList[moveNum], 4); // and back
5137       } else
5138       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5139        * the engine. It would be nice to have a better way to identify castle
5140        * moves here. */
5141       if(appData.fischerCastling && cps->useOOCastle) {
5142         int fromX = moveList[moveNum][0] - AAA;
5143         int fromY = moveList[moveNum][1] - ONE;
5144         int toX = moveList[moveNum][2] - AAA;
5145         int toY = moveList[moveNum][3] - ONE;
5146         if((boards[moveNum][fromY][fromX] == WhiteKing
5147             && boards[moveNum][toY][toX] == WhiteRook)
5148            || (boards[moveNum][fromY][fromX] == BlackKing
5149                && boards[moveNum][toY][toX] == BlackRook)) {
5150           if(toX > fromX) SendToProgram("O-O\n", cps);
5151           else SendToProgram("O-O-O\n", cps);
5152         }
5153         else SendToProgram(moveList[moveNum], cps);
5154       } else
5155       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5156         char *m = moveList[moveNum];
5157         static char c[2];
5158         *c = m[7]; // promoChar
5159         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5160           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5161                                                m[2], m[3] - '0',
5162                                                m[5], m[6] - '0',
5163                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5164         else
5165           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5166                                                m[5], m[6] - '0',
5167                                                m[5], m[6] - '0',
5168                                                m[2], m[3] - '0', c);
5169           SendToProgram(buf, cps);
5170       } else
5171       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5172         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5173           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5174           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5175                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5176         } else
5177           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5178                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5179         SendToProgram(buf, cps);
5180       }
5181       else SendToProgram(moveList[moveNum], cps);
5182       /* End of additions by Tord */
5183     }
5184
5185     /* [HGM] setting up the opening has brought engine in force mode! */
5186     /*       Send 'go' if we are in a mode where machine should play. */
5187     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5188         (gameMode == TwoMachinesPlay   ||
5189 #if ZIPPY
5190          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5191 #endif
5192          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5193         SendToProgram("go\n", cps);
5194   if (appData.debugMode) {
5195     fprintf(debugFP, "(extra)\n");
5196   }
5197     }
5198     setboardSpoiledMachineBlack = 0;
5199 }
5200
5201 void
5202 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5203 {
5204     char user_move[MSG_SIZ];
5205     char suffix[4];
5206
5207     if(gameInfo.variant == VariantSChess && promoChar) {
5208         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5209         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5210     } else suffix[0] = NULLCHAR;
5211
5212     switch (moveType) {
5213       default:
5214         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5215                 (int)moveType, fromX, fromY, toX, toY);
5216         DisplayError(user_move + strlen("say "), 0);
5217         break;
5218       case WhiteKingSideCastle:
5219       case BlackKingSideCastle:
5220       case WhiteQueenSideCastleWild:
5221       case BlackQueenSideCastleWild:
5222       /* PUSH Fabien */
5223       case WhiteHSideCastleFR:
5224       case BlackHSideCastleFR:
5225       /* POP Fabien */
5226         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5227         break;
5228       case WhiteQueenSideCastle:
5229       case BlackQueenSideCastle:
5230       case WhiteKingSideCastleWild:
5231       case BlackKingSideCastleWild:
5232       /* PUSH Fabien */
5233       case WhiteASideCastleFR:
5234       case BlackASideCastleFR:
5235       /* POP Fabien */
5236         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5237         break;
5238       case WhiteNonPromotion:
5239       case BlackNonPromotion:
5240         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5241         break;
5242       case WhitePromotion:
5243       case BlackPromotion:
5244         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5245            gameInfo.variant == VariantMakruk)
5246           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5247                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5248                 PieceToChar(WhiteFerz));
5249         else if(gameInfo.variant == VariantGreat)
5250           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5251                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5252                 PieceToChar(WhiteMan));
5253         else
5254           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5255                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5256                 promoChar);
5257         break;
5258       case WhiteDrop:
5259       case BlackDrop:
5260       drop:
5261         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5262                  ToUpper(PieceToChar((ChessSquare) fromX)),
5263                  AAA + toX, ONE + toY);
5264         break;
5265       case IllegalMove:  /* could be a variant we don't quite understand */
5266         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5267       case NormalMove:
5268       case WhiteCapturesEnPassant:
5269       case BlackCapturesEnPassant:
5270         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5272         break;
5273     }
5274     SendToICS(user_move);
5275     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5276         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5277 }
5278
5279 void
5280 UploadGameEvent ()
5281 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5282     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5283     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5284     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5285       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5286       return;
5287     }
5288     if(gameMode != IcsExamining) { // is this ever not the case?
5289         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5290
5291         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5292           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5293         } else { // on FICS we must first go to general examine mode
5294           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5295         }
5296         if(gameInfo.variant != VariantNormal) {
5297             // try figure out wild number, as xboard names are not always valid on ICS
5298             for(i=1; i<=36; i++) {
5299               snprintf(buf, MSG_SIZ, "wild/%d", i);
5300                 if(StringToVariant(buf) == gameInfo.variant) break;
5301             }
5302             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5303             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5304             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5305         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5306         SendToICS(ics_prefix);
5307         SendToICS(buf);
5308         if(startedFromSetupPosition || backwardMostMove != 0) {
5309           fen = PositionToFEN(backwardMostMove, NULL, 1);
5310           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5311             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5312             SendToICS(buf);
5313           } else { // FICS: everything has to set by separate bsetup commands
5314             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5315             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5316             SendToICS(buf);
5317             if(!WhiteOnMove(backwardMostMove)) {
5318                 SendToICS("bsetup tomove black\n");
5319             }
5320             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5321             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5322             SendToICS(buf);
5323             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5324             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5325             SendToICS(buf);
5326             i = boards[backwardMostMove][EP_STATUS];
5327             if(i >= 0) { // set e.p.
5328               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5329                 SendToICS(buf);
5330             }
5331             bsetup++;
5332           }
5333         }
5334       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5335             SendToICS("bsetup done\n"); // switch to normal examining.
5336     }
5337     for(i = backwardMostMove; i<last; i++) {
5338         char buf[20];
5339         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5340         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5341             int len = strlen(moveList[i]);
5342             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5343             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5344         }
5345         SendToICS(buf);
5346     }
5347     SendToICS(ics_prefix);
5348     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5349 }
5350
5351 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5352 int legNr = 1;
5353
5354 void
5355 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5356 {
5357     if (rf == DROP_RANK) {
5358       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5359       sprintf(move, "%c@%c%c\n",
5360                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5361     } else {
5362         if (promoChar == 'x' || promoChar == NULLCHAR) {
5363           sprintf(move, "%c%c%c%c\n",
5364                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5365           if(killX >= 0 && killY >= 0) {
5366             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5367             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5368           }
5369         } else {
5370             sprintf(move, "%c%c%c%c%c\n",
5371                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5372           if(killX >= 0 && killY >= 0) {
5373             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5374             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5375           }
5376         }
5377     }
5378 }
5379
5380 void
5381 ProcessICSInitScript (FILE *f)
5382 {
5383     char buf[MSG_SIZ];
5384
5385     while (fgets(buf, MSG_SIZ, f)) {
5386         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5387     }
5388
5389     fclose(f);
5390 }
5391
5392
5393 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5394 int dragging;
5395 static ClickType lastClickType;
5396
5397 int
5398 PieceInString (char *s, ChessSquare piece)
5399 {
5400   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5401   while((p = strchr(s, ID))) {
5402     if(!suffix || p[1] == suffix) return TRUE;
5403     s = p;
5404   }
5405   return FALSE;
5406 }
5407
5408 int
5409 Partner (ChessSquare *p)
5410 { // change piece into promotion partner if one shogi-promotes to the other
5411   ChessSquare partner = promoPartner[*p];
5412   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5413   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5414   *p = partner;
5415   return 1;
5416 }
5417
5418 void
5419 Sweep (int step)
5420 {
5421     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5422     static int toggleFlag;
5423     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5424     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5425     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5426     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5427     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5428     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5429     do {
5430         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5431         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5432         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5433         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5434         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5435         if(!step) step = -1;
5436     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5437             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5438             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5439             promoSweep == pawn ||
5440             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5441             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5442     if(toX >= 0) {
5443         int victim = boards[currentMove][toY][toX];
5444         boards[currentMove][toY][toX] = promoSweep;
5445         DrawPosition(FALSE, boards[currentMove]);
5446         boards[currentMove][toY][toX] = victim;
5447     } else
5448     ChangeDragPiece(promoSweep);
5449 }
5450
5451 int
5452 PromoScroll (int x, int y)
5453 {
5454   int step = 0;
5455
5456   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5457   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5458   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5459   if(!step) return FALSE;
5460   lastX = x; lastY = y;
5461   if((promoSweep < BlackPawn) == flipView) step = -step;
5462   if(step > 0) selectFlag = 1;
5463   if(!selectFlag) Sweep(step);
5464   return FALSE;
5465 }
5466
5467 void
5468 NextPiece (int step)
5469 {
5470     ChessSquare piece = boards[currentMove][toY][toX];
5471     do {
5472         pieceSweep -= step;
5473         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5474         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5475         if(!step) step = -1;
5476     } while(PieceToChar(pieceSweep) == '.');
5477     boards[currentMove][toY][toX] = pieceSweep;
5478     DrawPosition(FALSE, boards[currentMove]);
5479     boards[currentMove][toY][toX] = piece;
5480 }
5481 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5482 void
5483 AlphaRank (char *move, int n)
5484 {
5485 //    char *p = move, c; int x, y;
5486
5487     if (appData.debugMode) {
5488         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5489     }
5490
5491     if(move[1]=='*' &&
5492        move[2]>='0' && move[2]<='9' &&
5493        move[3]>='a' && move[3]<='x'    ) {
5494         move[1] = '@';
5495         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5496         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5497     } else
5498     if(move[0]>='0' && move[0]<='9' &&
5499        move[1]>='a' && move[1]<='x' &&
5500        move[2]>='0' && move[2]<='9' &&
5501        move[3]>='a' && move[3]<='x'    ) {
5502         /* input move, Shogi -> normal */
5503         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5504         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5505         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5506         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5507     } else
5508     if(move[1]=='@' &&
5509        move[3]>='0' && move[3]<='9' &&
5510        move[2]>='a' && move[2]<='x'    ) {
5511         move[1] = '*';
5512         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5513         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5514     } else
5515     if(
5516        move[0]>='a' && move[0]<='x' &&
5517        move[3]>='0' && move[3]<='9' &&
5518        move[2]>='a' && move[2]<='x'    ) {
5519          /* output move, normal -> Shogi */
5520         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5521         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5522         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5523         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5524         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5525     }
5526     if (appData.debugMode) {
5527         fprintf(debugFP, "   out = '%s'\n", move);
5528     }
5529 }
5530
5531 char yy_textstr[8000];
5532
5533 /* Parser for moves from gnuchess, ICS, or user typein box */
5534 Boolean
5535 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5536 {
5537     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5538
5539     switch (*moveType) {
5540       case WhitePromotion:
5541       case BlackPromotion:
5542       case WhiteNonPromotion:
5543       case BlackNonPromotion:
5544       case NormalMove:
5545       case FirstLeg:
5546       case WhiteCapturesEnPassant:
5547       case BlackCapturesEnPassant:
5548       case WhiteKingSideCastle:
5549       case WhiteQueenSideCastle:
5550       case BlackKingSideCastle:
5551       case BlackQueenSideCastle:
5552       case WhiteKingSideCastleWild:
5553       case WhiteQueenSideCastleWild:
5554       case BlackKingSideCastleWild:
5555       case BlackQueenSideCastleWild:
5556       /* Code added by Tord: */
5557       case WhiteHSideCastleFR:
5558       case WhiteASideCastleFR:
5559       case BlackHSideCastleFR:
5560       case BlackASideCastleFR:
5561       /* End of code added by Tord */
5562       case IllegalMove:         /* bug or odd chess variant */
5563         if(currentMoveString[1] == '@') { // illegal drop
5564           *fromX = WhiteOnMove(moveNum) ?
5565             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5566             (int) CharToPiece(ToLower(currentMoveString[0]));
5567           goto drop;
5568         }
5569         *fromX = currentMoveString[0] - AAA;
5570         *fromY = currentMoveString[1] - ONE;
5571         *toX = currentMoveString[2] - AAA;
5572         *toY = currentMoveString[3] - ONE;
5573         *promoChar = currentMoveString[4];
5574         if(*promoChar == ';') *promoChar = currentMoveString[7];
5575         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5576             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5577     if (appData.debugMode) {
5578         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5579     }
5580             *fromX = *fromY = *toX = *toY = 0;
5581             return FALSE;
5582         }
5583         if (appData.testLegality) {
5584           return (*moveType != IllegalMove);
5585         } else {
5586           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5587                          // [HGM] lion: if this is a double move we are less critical
5588                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5589         }
5590
5591       case WhiteDrop:
5592       case BlackDrop:
5593         *fromX = *moveType == WhiteDrop ?
5594           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5595           (int) CharToPiece(ToLower(currentMoveString[0]));
5596       drop:
5597         *fromY = DROP_RANK;
5598         *toX = currentMoveString[2] - AAA;
5599         *toY = currentMoveString[3] - ONE;
5600         *promoChar = NULLCHAR;
5601         return TRUE;
5602
5603       case AmbiguousMove:
5604       case ImpossibleMove:
5605       case EndOfFile:
5606       case ElapsedTime:
5607       case Comment:
5608       case PGNTag:
5609       case NAG:
5610       case WhiteWins:
5611       case BlackWins:
5612       case GameIsDrawn:
5613       default:
5614     if (appData.debugMode) {
5615         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5616     }
5617         /* bug? */
5618         *fromX = *fromY = *toX = *toY = 0;
5619         *promoChar = NULLCHAR;
5620         return FALSE;
5621     }
5622 }
5623
5624 Boolean pushed = FALSE;
5625 char *lastParseAttempt;
5626
5627 void
5628 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5629 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5630   int fromX, fromY, toX, toY; char promoChar;
5631   ChessMove moveType;
5632   Boolean valid;
5633   int nr = 0;
5634
5635   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5636   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5637     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5638     pushed = TRUE;
5639   }
5640   endPV = forwardMostMove;
5641   do {
5642     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5643     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5644     lastParseAttempt = pv;
5645     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5646     if(!valid && nr == 0 &&
5647        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5648         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5649         // Hande case where played move is different from leading PV move
5650         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5651         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5652         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5653         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5654           endPV += 2; // if position different, keep this
5655           moveList[endPV-1][0] = fromX + AAA;
5656           moveList[endPV-1][1] = fromY + ONE;
5657           moveList[endPV-1][2] = toX + AAA;
5658           moveList[endPV-1][3] = toY + ONE;
5659           parseList[endPV-1][0] = NULLCHAR;
5660           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5661         }
5662       }
5663     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5664     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5665     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5666     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5667         valid++; // allow comments in PV
5668         continue;
5669     }
5670     nr++;
5671     if(endPV+1 > framePtr) break; // no space, truncate
5672     if(!valid) break;
5673     endPV++;
5674     CopyBoard(boards[endPV], boards[endPV-1]);
5675     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5676     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5677     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5678     CoordsToAlgebraic(boards[endPV - 1],
5679                              PosFlags(endPV - 1),
5680                              fromY, fromX, toY, toX, promoChar,
5681                              parseList[endPV - 1]);
5682   } while(valid);
5683   if(atEnd == 2) return; // used hidden, for PV conversion
5684   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5685   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5686   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5687                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5688   DrawPosition(TRUE, boards[currentMove]);
5689 }
5690
5691 int
5692 MultiPV (ChessProgramState *cps, int kind)
5693 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5694         int i;
5695         for(i=0; i<cps->nrOptions; i++) {
5696             char *s = cps->option[i].name;
5697             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5698             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5699                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5700         }
5701         return -1;
5702 }
5703
5704 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5705 static int multi, pv_margin;
5706 static ChessProgramState *activeCps;
5707
5708 Boolean
5709 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5710 {
5711         int startPV, lineStart, origIndex = index;
5712         char *p, buf2[MSG_SIZ];
5713         ChessProgramState *cps = (pane ? &second : &first);
5714
5715         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5716         lastX = x; lastY = y;
5717         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5718         lineStart = startPV = index;
5719         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5720         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5721         index = startPV;
5722         do{ while(buf[index] && buf[index] != '\n') index++;
5723         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5724         buf[index] = 0;
5725         if(lineStart == 0 && gameMode == AnalyzeMode) {
5726             int n = 0;
5727             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5728             if(n == 0) { // click not on "fewer" or "more"
5729                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5730                     pv_margin = cps->option[multi].value;
5731                     activeCps = cps; // non-null signals margin adjustment
5732                 }
5733             } else if((multi = MultiPV(cps, 1)) >= 0) {
5734                 n += cps->option[multi].value; if(n < 1) n = 1;
5735                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5736                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5737                 cps->option[multi].value = n;
5738                 *start = *end = 0;
5739                 return FALSE;
5740             }
5741         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5742                 ExcludeClick(origIndex - lineStart);
5743                 return FALSE;
5744         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5745                 Collapse(origIndex - lineStart);
5746                 return FALSE;
5747         }
5748         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5749         *start = startPV; *end = index-1;
5750         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5751         return TRUE;
5752 }
5753
5754 char *
5755 PvToSAN (char *pv)
5756 {
5757         static char buf[10*MSG_SIZ];
5758         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5759         *buf = NULLCHAR;
5760         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5761         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5762         for(i = forwardMostMove; i<endPV; i++){
5763             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5764             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5765             k += strlen(buf+k);
5766         }
5767         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5768         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5769         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5770         endPV = savedEnd;
5771         return buf;
5772 }
5773
5774 Boolean
5775 LoadPV (int x, int y)
5776 { // called on right mouse click to load PV
5777   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5778   lastX = x; lastY = y;
5779   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5780   extendGame = FALSE;
5781   return TRUE;
5782 }
5783
5784 void
5785 UnLoadPV ()
5786 {
5787   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5788   if(activeCps) {
5789     if(pv_margin != activeCps->option[multi].value) {
5790       char buf[MSG_SIZ];
5791       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5792       SendToProgram(buf, activeCps);
5793       activeCps->option[multi].value = pv_margin;
5794     }
5795     activeCps = NULL;
5796     return;
5797   }
5798   if(endPV < 0) return;
5799   if(appData.autoCopyPV) CopyFENToClipboard();
5800   endPV = -1;
5801   if(extendGame && currentMove > forwardMostMove) {
5802         Boolean saveAnimate = appData.animate;
5803         if(pushed) {
5804             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5805                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5806             } else storedGames--; // abandon shelved tail of original game
5807         }
5808         pushed = FALSE;
5809         forwardMostMove = currentMove;
5810         currentMove = oldFMM;
5811         appData.animate = FALSE;
5812         ToNrEvent(forwardMostMove);
5813         appData.animate = saveAnimate;
5814   }
5815   currentMove = forwardMostMove;
5816   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5817   ClearPremoveHighlights();
5818   DrawPosition(TRUE, boards[currentMove]);
5819 }
5820
5821 void
5822 MovePV (int x, int y, int h)
5823 { // step through PV based on mouse coordinates (called on mouse move)
5824   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5825
5826   if(activeCps) { // adjusting engine's multi-pv margin
5827     if(x > lastX) pv_margin++; else
5828     if(x < lastX) pv_margin -= (pv_margin > 0);
5829     if(x != lastX) {
5830       char buf[MSG_SIZ];
5831       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5832       DisplayMessage(buf, "");
5833     }
5834     lastX = x;
5835     return;
5836   }
5837   // we must somehow check if right button is still down (might be released off board!)
5838   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5839   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5840   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5841   if(!step) return;
5842   lastX = x; lastY = y;
5843
5844   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5845   if(endPV < 0) return;
5846   if(y < margin) step = 1; else
5847   if(y > h - margin) step = -1;
5848   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5849   currentMove += step;
5850   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5851   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5852                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5853   DrawPosition(FALSE, boards[currentMove]);
5854 }
5855
5856
5857 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5858 // All positions will have equal probability, but the current method will not provide a unique
5859 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5860 #define DARK 1
5861 #define LITE 2
5862 #define ANY 3
5863
5864 int squaresLeft[4];
5865 int piecesLeft[(int)BlackPawn];
5866 int seed, nrOfShuffles;
5867
5868 void
5869 GetPositionNumber ()
5870 {       // sets global variable seed
5871         int i;
5872
5873         seed = appData.defaultFrcPosition;
5874         if(seed < 0) { // randomize based on time for negative FRC position numbers
5875                 for(i=0; i<50; i++) seed += random();
5876                 seed = random() ^ random() >> 8 ^ random() << 8;
5877                 if(seed<0) seed = -seed;
5878         }
5879 }
5880
5881 int
5882 put (Board board, int pieceType, int rank, int n, int shade)
5883 // put the piece on the (n-1)-th empty squares of the given shade
5884 {
5885         int i;
5886
5887         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5888                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5889                         board[rank][i] = (ChessSquare) pieceType;
5890                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5891                         squaresLeft[ANY]--;
5892                         piecesLeft[pieceType]--;
5893                         return i;
5894                 }
5895         }
5896         return -1;
5897 }
5898
5899
5900 void
5901 AddOnePiece (Board board, int pieceType, int rank, int shade)
5902 // calculate where the next piece goes, (any empty square), and put it there
5903 {
5904         int i;
5905
5906         i = seed % squaresLeft[shade];
5907         nrOfShuffles *= squaresLeft[shade];
5908         seed /= squaresLeft[shade];
5909         put(board, pieceType, rank, i, shade);
5910 }
5911
5912 void
5913 AddTwoPieces (Board board, int pieceType, int rank)
5914 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5915 {
5916         int i, n=squaresLeft[ANY], j=n-1, k;
5917
5918         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5919         i = seed % k;  // pick one
5920         nrOfShuffles *= k;
5921         seed /= k;
5922         while(i >= j) i -= j--;
5923         j = n - 1 - j; i += j;
5924         put(board, pieceType, rank, j, ANY);
5925         put(board, pieceType, rank, i, ANY);
5926 }
5927
5928 void
5929 SetUpShuffle (Board board, int number)
5930 {
5931         int i, p, first=1;
5932
5933         GetPositionNumber(); nrOfShuffles = 1;
5934
5935         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5936         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5937         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5938
5939         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5940
5941         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5942             p = (int) board[0][i];
5943             if(p < (int) BlackPawn) piecesLeft[p] ++;
5944             board[0][i] = EmptySquare;
5945         }
5946
5947         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5948             // shuffles restricted to allow normal castling put KRR first
5949             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5950                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5951             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5952                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5953             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5954                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5955             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5956                 put(board, WhiteRook, 0, 0, ANY);
5957             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5958         }
5959
5960         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5961             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5962             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5963                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5964                 while(piecesLeft[p] >= 2) {
5965                     AddOnePiece(board, p, 0, LITE);
5966                     AddOnePiece(board, p, 0, DARK);
5967                 }
5968                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5969             }
5970
5971         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5972             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5973             // but we leave King and Rooks for last, to possibly obey FRC restriction
5974             if(p == (int)WhiteRook) continue;
5975             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5976             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5977         }
5978
5979         // now everything is placed, except perhaps King (Unicorn) and Rooks
5980
5981         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5982             // Last King gets castling rights
5983             while(piecesLeft[(int)WhiteUnicorn]) {
5984                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5985                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5986             }
5987
5988             while(piecesLeft[(int)WhiteKing]) {
5989                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5990                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5991             }
5992
5993
5994         } else {
5995             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5996             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5997         }
5998
5999         // Only Rooks can be left; simply place them all
6000         while(piecesLeft[(int)WhiteRook]) {
6001                 i = put(board, WhiteRook, 0, 0, ANY);
6002                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6003                         if(first) {
6004                                 first=0;
6005                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6006                         }
6007                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6008                 }
6009         }
6010         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6011             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6012         }
6013
6014         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6015 }
6016
6017 int
6018 ptclen (const char *s, char *escapes)
6019 {
6020     int n = 0;
6021     if(!*escapes) return strlen(s);
6022     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6023     return n;
6024 }
6025
6026 int
6027 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6028 /* [HGM] moved here from winboard.c because of its general usefulness */
6029 /*       Basically a safe strcpy that uses the last character as King */
6030 {
6031     int result = FALSE; int NrPieces;
6032     unsigned char partner[EmptySquare];
6033
6034     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6035                     && NrPieces >= 12 && !(NrPieces&1)) {
6036         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6037
6038         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6039         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6040             char *p, c=0;
6041             if(map[j] == '/') offs = WhitePBishop - i, j++;
6042             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6043             table[i+offs] = map[j++];
6044             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6045             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6046             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6047         }
6048         table[(int) WhiteKing]  = map[j++];
6049         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6050             char *p, c=0;
6051             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6052             i = WHITE_TO_BLACK ii;
6053             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6054             table[i+offs] = map[j++];
6055             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6056             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6057             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6058         }
6059         table[(int) BlackKing]  = map[j++];
6060
6061
6062         if(*escapes) { // set up promotion pairing
6063             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6064             // pieceToChar entirely filled, so we can look up specified partners
6065             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6066                 int c = table[i];
6067                 if(c == '^' || c == '-') { // has specified partner
6068                     int p;
6069                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6070                     if(c == '^') table[i] = '+';
6071                     if(p < EmptySquare) {
6072                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6073                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6074                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6075                     }
6076                 } else if(c == '*') {
6077                     table[i] = partner[i];
6078                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6079                 }
6080             }
6081         }
6082
6083         result = TRUE;
6084     }
6085
6086     return result;
6087 }
6088
6089 int
6090 SetCharTable (unsigned char *table, const char * map)
6091 {
6092     return SetCharTableEsc(table, map, "");
6093 }
6094
6095 void
6096 Prelude (Board board)
6097 {       // [HGM] superchess: random selection of exo-pieces
6098         int i, j, k; ChessSquare p;
6099         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6100
6101         GetPositionNumber(); // use FRC position number
6102
6103         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6104             SetCharTable(pieceToChar, appData.pieceToCharTable);
6105             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6106                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6107         }
6108
6109         j = seed%4;                 seed /= 4;
6110         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6111         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6112         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6113         j = seed%3 + (seed%3 >= j); seed /= 3;
6114         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6115         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6116         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6117         j = seed%3;                 seed /= 3;
6118         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6119         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6120         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6121         j = seed%2 + (seed%2 >= j); seed /= 2;
6122         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6123         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6124         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6125         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6126         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6127         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6128         put(board, exoPieces[0],    0, 0, ANY);
6129         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6130 }
6131
6132 void
6133 InitPosition (int redraw)
6134 {
6135     ChessSquare (* pieces)[BOARD_FILES];
6136     int i, j, pawnRow=1, pieceRows=1, overrule,
6137     oldx = gameInfo.boardWidth,
6138     oldy = gameInfo.boardHeight,
6139     oldh = gameInfo.holdingsWidth;
6140     static int oldv;
6141
6142     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6143
6144     /* [AS] Initialize pv info list [HGM] and game status */
6145     {
6146         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6147             pvInfoList[i].depth = 0;
6148             boards[i][EP_STATUS] = EP_NONE;
6149             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6150         }
6151
6152         initialRulePlies = 0; /* 50-move counter start */
6153
6154         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6155         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6156     }
6157
6158
6159     /* [HGM] logic here is completely changed. In stead of full positions */
6160     /* the initialized data only consist of the two backranks. The switch */
6161     /* selects which one we will use, which is than copied to the Board   */
6162     /* initialPosition, which for the rest is initialized by Pawns and    */
6163     /* empty squares. This initial position is then copied to boards[0],  */
6164     /* possibly after shuffling, so that it remains available.            */
6165
6166     gameInfo.holdingsWidth = 0; /* default board sizes */
6167     gameInfo.boardWidth    = 8;
6168     gameInfo.boardHeight   = 8;
6169     gameInfo.holdingsSize  = 0;
6170     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6171     for(i=0; i<BOARD_FILES-6; i++)
6172       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6173     initialPosition[EP_STATUS] = EP_NONE;
6174     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6175     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6176     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6177          SetCharTable(pieceNickName, appData.pieceNickNames);
6178     else SetCharTable(pieceNickName, "............");
6179     pieces = FIDEArray;
6180
6181     switch (gameInfo.variant) {
6182     case VariantFischeRandom:
6183       shuffleOpenings = TRUE;
6184       appData.fischerCastling = TRUE;
6185     default:
6186       break;
6187     case VariantShatranj:
6188       pieces = ShatranjArray;
6189       nrCastlingRights = 0;
6190       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6191       break;
6192     case VariantMakruk:
6193       pieces = makrukArray;
6194       nrCastlingRights = 0;
6195       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6196       break;
6197     case VariantASEAN:
6198       pieces = aseanArray;
6199       nrCastlingRights = 0;
6200       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6201       break;
6202     case VariantTwoKings:
6203       pieces = twoKingsArray;
6204       break;
6205     case VariantGrand:
6206       pieces = GrandArray;
6207       nrCastlingRights = 0;
6208       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6209       gameInfo.boardWidth = 10;
6210       gameInfo.boardHeight = 10;
6211       gameInfo.holdingsSize = 7;
6212       break;
6213     case VariantCapaRandom:
6214       shuffleOpenings = TRUE;
6215       appData.fischerCastling = TRUE;
6216     case VariantCapablanca:
6217       pieces = CapablancaArray;
6218       gameInfo.boardWidth = 10;
6219       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6220       break;
6221     case VariantGothic:
6222       pieces = GothicArray;
6223       gameInfo.boardWidth = 10;
6224       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6225       break;
6226     case VariantSChess:
6227       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6228       gameInfo.holdingsSize = 7;
6229       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6230       break;
6231     case VariantJanus:
6232       pieces = JanusArray;
6233       gameInfo.boardWidth = 10;
6234       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6235       nrCastlingRights = 6;
6236         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6237         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6238         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6239         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6240         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6241         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6242       break;
6243     case VariantFalcon:
6244       pieces = FalconArray;
6245       gameInfo.boardWidth = 10;
6246       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6247       break;
6248     case VariantXiangqi:
6249       pieces = XiangqiArray;
6250       gameInfo.boardWidth  = 9;
6251       gameInfo.boardHeight = 10;
6252       nrCastlingRights = 0;
6253       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6254       break;
6255     case VariantShogi:
6256       pieces = ShogiArray;
6257       gameInfo.boardWidth  = 9;
6258       gameInfo.boardHeight = 9;
6259       gameInfo.holdingsSize = 7;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6262       break;
6263     case VariantChu:
6264       pieces = ChuArray; pieceRows = 3;
6265       gameInfo.boardWidth  = 12;
6266       gameInfo.boardHeight = 12;
6267       nrCastlingRights = 0;
6268       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6269                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6270       break;
6271     case VariantCourier:
6272       pieces = CourierArray;
6273       gameInfo.boardWidth  = 12;
6274       nrCastlingRights = 0;
6275       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6276       break;
6277     case VariantKnightmate:
6278       pieces = KnightmateArray;
6279       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6280       break;
6281     case VariantSpartan:
6282       pieces = SpartanArray;
6283       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6284       break;
6285     case VariantLion:
6286       pieces = lionArray;
6287       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6288       break;
6289     case VariantChuChess:
6290       pieces = ChuChessArray;
6291       gameInfo.boardWidth = 10;
6292       gameInfo.boardHeight = 10;
6293       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6294       break;
6295     case VariantFairy:
6296       pieces = fairyArray;
6297       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6298       break;
6299     case VariantGreat:
6300       pieces = GreatArray;
6301       gameInfo.boardWidth = 10;
6302       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6303       gameInfo.holdingsSize = 8;
6304       break;
6305     case VariantSuper:
6306       pieces = FIDEArray;
6307       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6308       gameInfo.holdingsSize = 8;
6309       startedFromSetupPosition = TRUE;
6310       break;
6311     case VariantCrazyhouse:
6312     case VariantBughouse:
6313       pieces = FIDEArray;
6314       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6315       gameInfo.holdingsSize = 5;
6316       break;
6317     case VariantWildCastle:
6318       pieces = FIDEArray;
6319       /* !!?shuffle with kings guaranteed to be on d or e file */
6320       shuffleOpenings = 1;
6321       break;
6322     case VariantNoCastle:
6323       pieces = FIDEArray;
6324       nrCastlingRights = 0;
6325       /* !!?unconstrained back-rank shuffle */
6326       shuffleOpenings = 1;
6327       break;
6328     }
6329
6330     overrule = 0;
6331     if(appData.NrFiles >= 0) {
6332         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6333         gameInfo.boardWidth = appData.NrFiles;
6334     }
6335     if(appData.NrRanks >= 0) {
6336         gameInfo.boardHeight = appData.NrRanks;
6337     }
6338     if(appData.holdingsSize >= 0) {
6339         i = appData.holdingsSize;
6340         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6341         gameInfo.holdingsSize = i;
6342     }
6343     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6344     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6345         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6346
6347     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6348     if(pawnRow < 1) pawnRow = 1;
6349     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6350        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6351     if(gameInfo.variant == VariantChu) pawnRow = 3;
6352
6353     /* User pieceToChar list overrules defaults */
6354     if(appData.pieceToCharTable != NULL)
6355         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6356
6357     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6358
6359         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6360             s = (ChessSquare) 0; /* account holding counts in guard band */
6361         for( i=0; i<BOARD_HEIGHT; i++ )
6362             initialPosition[i][j] = s;
6363
6364         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6365         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6366         initialPosition[pawnRow][j] = WhitePawn;
6367         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6368         if(gameInfo.variant == VariantXiangqi) {
6369             if(j&1) {
6370                 initialPosition[pawnRow][j] =
6371                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6372                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6373                    initialPosition[2][j] = WhiteCannon;
6374                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6375                 }
6376             }
6377         }
6378         if(gameInfo.variant == VariantChu) {
6379              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6380                initialPosition[pawnRow+1][j] = WhiteCobra,
6381                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6382              for(i=1; i<pieceRows; i++) {
6383                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6384                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6385              }
6386         }
6387         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6388             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6389                initialPosition[0][j] = WhiteRook;
6390                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6391             }
6392         }
6393         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6394     }
6395     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6396     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6397
6398             j=BOARD_LEFT+1;
6399             initialPosition[1][j] = WhiteBishop;
6400             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6401             j=BOARD_RGHT-2;
6402             initialPosition[1][j] = WhiteRook;
6403             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6404     }
6405
6406     if( nrCastlingRights == -1) {
6407         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6408         /*       This sets default castling rights from none to normal corners   */
6409         /* Variants with other castling rights must set them themselves above    */
6410         nrCastlingRights = 6;
6411
6412         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6413         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6414         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6415         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6416         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6417         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6418      }
6419
6420      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6421      if(gameInfo.variant == VariantGreat) { // promotion commoners
6422         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6423         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6424         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6425         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6426      }
6427      if( gameInfo.variant == VariantSChess ) {
6428       initialPosition[1][0] = BlackMarshall;
6429       initialPosition[2][0] = BlackAngel;
6430       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6431       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6432       initialPosition[1][1] = initialPosition[2][1] =
6433       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6434      }
6435   if (appData.debugMode) {
6436     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6437   }
6438     if(shuffleOpenings) {
6439         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6440         startedFromSetupPosition = TRUE;
6441     }
6442     if(startedFromPositionFile) {
6443       /* [HGM] loadPos: use PositionFile for every new game */
6444       CopyBoard(initialPosition, filePosition);
6445       for(i=0; i<nrCastlingRights; i++)
6446           initialRights[i] = filePosition[CASTLING][i];
6447       startedFromSetupPosition = TRUE;
6448     }
6449
6450     CopyBoard(boards[0], initialPosition);
6451
6452     if(oldx != gameInfo.boardWidth ||
6453        oldy != gameInfo.boardHeight ||
6454        oldv != gameInfo.variant ||
6455        oldh != gameInfo.holdingsWidth
6456                                          )
6457             InitDrawingSizes(-2 ,0);
6458
6459     oldv = gameInfo.variant;
6460     if (redraw)
6461       DrawPosition(TRUE, boards[currentMove]);
6462 }
6463
6464 void
6465 SendBoard (ChessProgramState *cps, int moveNum)
6466 {
6467     char message[MSG_SIZ];
6468
6469     if (cps->useSetboard) {
6470       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6471       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6472       SendToProgram(message, cps);
6473       free(fen);
6474
6475     } else {
6476       ChessSquare *bp;
6477       int i, j, left=0, right=BOARD_WIDTH;
6478       /* Kludge to set black to move, avoiding the troublesome and now
6479        * deprecated "black" command.
6480        */
6481       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6482         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6483
6484       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6485
6486       SendToProgram("edit\n", cps);
6487       SendToProgram("#\n", cps);
6488       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6489         bp = &boards[moveNum][i][left];
6490         for (j = left; j < right; j++, bp++) {
6491           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6492           if ((int) *bp < (int) BlackPawn) {
6493             if(j == BOARD_RGHT+1)
6494                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6495             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6496             if(message[0] == '+' || message[0] == '~') {
6497               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6498                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6499                         AAA + j, ONE + i - '0');
6500             }
6501             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6502                 message[1] = BOARD_RGHT   - 1 - j + '1';
6503                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6504             }
6505             SendToProgram(message, cps);
6506           }
6507         }
6508       }
6509
6510       SendToProgram("c\n", cps);
6511       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6512         bp = &boards[moveNum][i][left];
6513         for (j = left; j < right; j++, bp++) {
6514           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6515           if (((int) *bp != (int) EmptySquare)
6516               && ((int) *bp >= (int) BlackPawn)) {
6517             if(j == BOARD_LEFT-2)
6518                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6519             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6520                     AAA + j, ONE + i - '0');
6521             if(message[0] == '+' || message[0] == '~') {
6522               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6523                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6524                         AAA + j, ONE + i - '0');
6525             }
6526             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6527                 message[1] = BOARD_RGHT   - 1 - j + '1';
6528                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6529             }
6530             SendToProgram(message, cps);
6531           }
6532         }
6533       }
6534
6535       SendToProgram(".\n", cps);
6536     }
6537     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6538 }
6539
6540 char exclusionHeader[MSG_SIZ];
6541 int exCnt, excludePtr;
6542 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6543 static Exclusion excluTab[200];
6544 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6545
6546 static void
6547 WriteMap (int s)
6548 {
6549     int j;
6550     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6551     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6552 }
6553
6554 static void
6555 ClearMap ()
6556 {
6557     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6558     excludePtr = 24; exCnt = 0;
6559     WriteMap(0);
6560 }
6561
6562 static void
6563 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6564 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6565     char buf[2*MOVE_LEN], *p;
6566     Exclusion *e = excluTab;
6567     int i;
6568     for(i=0; i<exCnt; i++)
6569         if(e[i].ff == fromX && e[i].fr == fromY &&
6570            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6571     if(i == exCnt) { // was not in exclude list; add it
6572         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6573         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6574             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6575             return; // abort
6576         }
6577         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6578         excludePtr++; e[i].mark = excludePtr++;
6579         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6580         exCnt++;
6581     }
6582     exclusionHeader[e[i].mark] = state;
6583 }
6584
6585 static int
6586 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6587 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6588     char buf[MSG_SIZ];
6589     int j, k;
6590     ChessMove moveType;
6591     if((signed char)promoChar == -1) { // kludge to indicate best move
6592         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6593             return 1; // if unparsable, abort
6594     }
6595     // update exclusion map (resolving toggle by consulting existing state)
6596     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6597     j = k%8; k >>= 3;
6598     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6599     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6600          excludeMap[k] |=   1<<j;
6601     else excludeMap[k] &= ~(1<<j);
6602     // update header
6603     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6604     // inform engine
6605     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6606     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6607     SendToBoth(buf);
6608     return (state == '+');
6609 }
6610
6611 static void
6612 ExcludeClick (int index)
6613 {
6614     int i, j;
6615     Exclusion *e = excluTab;
6616     if(index < 25) { // none, best or tail clicked
6617         if(index < 13) { // none: include all
6618             WriteMap(0); // clear map
6619             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6620             SendToBoth("include all\n"); // and inform engine
6621         } else if(index > 18) { // tail
6622             if(exclusionHeader[19] == '-') { // tail was excluded
6623                 SendToBoth("include all\n");
6624                 WriteMap(0); // clear map completely
6625                 // now re-exclude selected moves
6626                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6627                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6628             } else { // tail was included or in mixed state
6629                 SendToBoth("exclude all\n");
6630                 WriteMap(0xFF); // fill map completely
6631                 // now re-include selected moves
6632                 j = 0; // count them
6633                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6634                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6635                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6636             }
6637         } else { // best
6638             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6639         }
6640     } else {
6641         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6642             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6643             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6644             break;
6645         }
6646     }
6647 }
6648
6649 ChessSquare
6650 DefaultPromoChoice (int white)
6651 {
6652     ChessSquare result;
6653     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6654        gameInfo.variant == VariantMakruk)
6655         result = WhiteFerz; // no choice
6656     else if(gameInfo.variant == VariantASEAN)
6657         result = WhiteRook; // no choice
6658     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6659         result= WhiteKing; // in Suicide Q is the last thing we want
6660     else if(gameInfo.variant == VariantSpartan)
6661         result = white ? WhiteQueen : WhiteAngel;
6662     else result = WhiteQueen;
6663     if(!white) result = WHITE_TO_BLACK result;
6664     return result;
6665 }
6666
6667 static int autoQueen; // [HGM] oneclick
6668
6669 int
6670 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6671 {
6672     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6673     /* [HGM] add Shogi promotions */
6674     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6675     ChessSquare piece, partner;
6676     ChessMove moveType;
6677     Boolean premove;
6678
6679     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6680     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6681
6682     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6683       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6684         return FALSE;
6685
6686     piece = boards[currentMove][fromY][fromX];
6687     if(gameInfo.variant == VariantChu) {
6688         promotionZoneSize = BOARD_HEIGHT/3;
6689         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6690     } else if(gameInfo.variant == VariantShogi) {
6691         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6692         highestPromotingPiece = (int)WhiteAlfil;
6693     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6694         promotionZoneSize = 3;
6695     }
6696
6697     // Treat Lance as Pawn when it is not representing Amazon or Lance
6698     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6699         if(piece == WhiteLance) piece = WhitePawn; else
6700         if(piece == BlackLance) piece = BlackPawn;
6701     }
6702
6703     // next weed out all moves that do not touch the promotion zone at all
6704     if((int)piece >= BlackPawn) {
6705         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6706              return FALSE;
6707         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6708         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6709     } else {
6710         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6711            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6712         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6713              return FALSE;
6714     }
6715
6716     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6717
6718     // weed out mandatory Shogi promotions
6719     if(gameInfo.variant == VariantShogi) {
6720         if(piece >= BlackPawn) {
6721             if(toY == 0 && piece == BlackPawn ||
6722                toY == 0 && piece == BlackQueen ||
6723                toY <= 1 && piece == BlackKnight) {
6724                 *promoChoice = '+';
6725                 return FALSE;
6726             }
6727         } else {
6728             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6729                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6730                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6731                 *promoChoice = '+';
6732                 return FALSE;
6733             }
6734         }
6735     }
6736
6737     // weed out obviously illegal Pawn moves
6738     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6739         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6740         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6741         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6742         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6743         // note we are not allowed to test for valid (non-)capture, due to premove
6744     }
6745
6746     // we either have a choice what to promote to, or (in Shogi) whether to promote
6747     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6748        gameInfo.variant == VariantMakruk) {
6749         ChessSquare p=BlackFerz;  // no choice
6750         while(p < EmptySquare) {  //but make sure we use piece that exists
6751             *promoChoice = PieceToChar(p++);
6752             if(*promoChoice != '.') break;
6753         }
6754         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6755     }
6756     // no sense asking what we must promote to if it is going to explode...
6757     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6758         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6759         return FALSE;
6760     }
6761     // give caller the default choice even if we will not make it
6762     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6763     partner = piece; // pieces can promote if the pieceToCharTable says so
6764     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6765     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6766     if(        sweepSelect && gameInfo.variant != VariantGreat
6767                            && gameInfo.variant != VariantGrand
6768                            && gameInfo.variant != VariantSuper) return FALSE;
6769     if(autoQueen) return FALSE; // predetermined
6770
6771     // suppress promotion popup on illegal moves that are not premoves
6772     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6773               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6774     if(appData.testLegality && !premove) {
6775         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6776                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6777         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6778         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6779             return FALSE;
6780     }
6781
6782     return TRUE;
6783 }
6784
6785 int
6786 InPalace (int row, int column)
6787 {   /* [HGM] for Xiangqi */
6788     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6789          column < (BOARD_WIDTH + 4)/2 &&
6790          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6791     return FALSE;
6792 }
6793
6794 int
6795 PieceForSquare (int x, int y)
6796 {
6797   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6798      return -1;
6799   else
6800      return boards[currentMove][y][x];
6801 }
6802
6803 int
6804 OKToStartUserMove (int x, int y)
6805 {
6806     ChessSquare from_piece;
6807     int white_piece;
6808
6809     if (matchMode) return FALSE;
6810     if (gameMode == EditPosition) return TRUE;
6811
6812     if (x >= 0 && y >= 0)
6813       from_piece = boards[currentMove][y][x];
6814     else
6815       from_piece = EmptySquare;
6816
6817     if (from_piece == EmptySquare) return FALSE;
6818
6819     white_piece = (int)from_piece >= (int)WhitePawn &&
6820       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6821
6822     switch (gameMode) {
6823       case AnalyzeFile:
6824       case TwoMachinesPlay:
6825       case EndOfGame:
6826         return FALSE;
6827
6828       case IcsObserving:
6829       case IcsIdle:
6830         return FALSE;
6831
6832       case MachinePlaysWhite:
6833       case IcsPlayingBlack:
6834         if (appData.zippyPlay) return FALSE;
6835         if (white_piece) {
6836             DisplayMoveError(_("You are playing Black"));
6837             return FALSE;
6838         }
6839         break;
6840
6841       case MachinePlaysBlack:
6842       case IcsPlayingWhite:
6843         if (appData.zippyPlay) return FALSE;
6844         if (!white_piece) {
6845             DisplayMoveError(_("You are playing White"));
6846             return FALSE;
6847         }
6848         break;
6849
6850       case PlayFromGameFile:
6851             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6852       case EditGame:
6853         if (!white_piece && WhiteOnMove(currentMove)) {
6854             DisplayMoveError(_("It is White's turn"));
6855             return FALSE;
6856         }
6857         if (white_piece && !WhiteOnMove(currentMove)) {
6858             DisplayMoveError(_("It is Black's turn"));
6859             return FALSE;
6860         }
6861         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6862             /* Editing correspondence game history */
6863             /* Could disallow this or prompt for confirmation */
6864             cmailOldMove = -1;
6865         }
6866         break;
6867
6868       case BeginningOfGame:
6869         if (appData.icsActive) return FALSE;
6870         if (!appData.noChessProgram) {
6871             if (!white_piece) {
6872                 DisplayMoveError(_("You are playing White"));
6873                 return FALSE;
6874             }
6875         }
6876         break;
6877
6878       case Training:
6879         if (!white_piece && WhiteOnMove(currentMove)) {
6880             DisplayMoveError(_("It is White's turn"));
6881             return FALSE;
6882         }
6883         if (white_piece && !WhiteOnMove(currentMove)) {
6884             DisplayMoveError(_("It is Black's turn"));
6885             return FALSE;
6886         }
6887         break;
6888
6889       default:
6890       case IcsExamining:
6891         break;
6892     }
6893     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6894         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6895         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6896         && gameMode != AnalyzeFile && gameMode != Training) {
6897         DisplayMoveError(_("Displayed position is not current"));
6898         return FALSE;
6899     }
6900     return TRUE;
6901 }
6902
6903 Boolean
6904 OnlyMove (int *x, int *y, Boolean captures)
6905 {
6906     DisambiguateClosure cl;
6907     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6908     switch(gameMode) {
6909       case MachinePlaysBlack:
6910       case IcsPlayingWhite:
6911       case BeginningOfGame:
6912         if(!WhiteOnMove(currentMove)) return FALSE;
6913         break;
6914       case MachinePlaysWhite:
6915       case IcsPlayingBlack:
6916         if(WhiteOnMove(currentMove)) return FALSE;
6917         break;
6918       case EditGame:
6919         break;
6920       default:
6921         return FALSE;
6922     }
6923     cl.pieceIn = EmptySquare;
6924     cl.rfIn = *y;
6925     cl.ffIn = *x;
6926     cl.rtIn = -1;
6927     cl.ftIn = -1;
6928     cl.promoCharIn = NULLCHAR;
6929     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6930     if( cl.kind == NormalMove ||
6931         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6932         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6933         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6934       fromX = cl.ff;
6935       fromY = cl.rf;
6936       *x = cl.ft;
6937       *y = cl.rt;
6938       return TRUE;
6939     }
6940     if(cl.kind != ImpossibleMove) return FALSE;
6941     cl.pieceIn = EmptySquare;
6942     cl.rfIn = -1;
6943     cl.ffIn = -1;
6944     cl.rtIn = *y;
6945     cl.ftIn = *x;
6946     cl.promoCharIn = NULLCHAR;
6947     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6948     if( cl.kind == NormalMove ||
6949         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6950         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6951         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6952       fromX = cl.ff;
6953       fromY = cl.rf;
6954       *x = cl.ft;
6955       *y = cl.rt;
6956       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6957       return TRUE;
6958     }
6959     return FALSE;
6960 }
6961
6962 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6963 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6964 int lastLoadGameUseList = FALSE;
6965 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6966 ChessMove lastLoadGameStart = EndOfFile;
6967 int doubleClick;
6968 Boolean addToBookFlag;
6969
6970 void
6971 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6972 {
6973     ChessMove moveType;
6974     ChessSquare pup;
6975     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6976
6977     /* Check if the user is playing in turn.  This is complicated because we
6978        let the user "pick up" a piece before it is his turn.  So the piece he
6979        tried to pick up may have been captured by the time he puts it down!
6980        Therefore we use the color the user is supposed to be playing in this
6981        test, not the color of the piece that is currently on the starting
6982        square---except in EditGame mode, where the user is playing both
6983        sides; fortunately there the capture race can't happen.  (It can
6984        now happen in IcsExamining mode, but that's just too bad.  The user
6985        will get a somewhat confusing message in that case.)
6986        */
6987
6988     switch (gameMode) {
6989       case AnalyzeFile:
6990       case TwoMachinesPlay:
6991       case EndOfGame:
6992       case IcsObserving:
6993       case IcsIdle:
6994         /* We switched into a game mode where moves are not accepted,
6995            perhaps while the mouse button was down. */
6996         return;
6997
6998       case MachinePlaysWhite:
6999         /* User is moving for Black */
7000         if (WhiteOnMove(currentMove)) {
7001             DisplayMoveError(_("It is White's turn"));
7002             return;
7003         }
7004         break;
7005
7006       case MachinePlaysBlack:
7007         /* User is moving for White */
7008         if (!WhiteOnMove(currentMove)) {
7009             DisplayMoveError(_("It is Black's turn"));
7010             return;
7011         }
7012         break;
7013
7014       case PlayFromGameFile:
7015             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7016       case EditGame:
7017       case IcsExamining:
7018       case BeginningOfGame:
7019       case AnalyzeMode:
7020       case Training:
7021         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7022         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7023             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7024             /* User is moving for Black */
7025             if (WhiteOnMove(currentMove)) {
7026                 DisplayMoveError(_("It is White's turn"));
7027                 return;
7028             }
7029         } else {
7030             /* User is moving for White */
7031             if (!WhiteOnMove(currentMove)) {
7032                 DisplayMoveError(_("It is Black's turn"));
7033                 return;
7034             }
7035         }
7036         break;
7037
7038       case IcsPlayingBlack:
7039         /* User is moving for Black */
7040         if (WhiteOnMove(currentMove)) {
7041             if (!appData.premove) {
7042                 DisplayMoveError(_("It is White's turn"));
7043             } else if (toX >= 0 && toY >= 0) {
7044                 premoveToX = toX;
7045                 premoveToY = toY;
7046                 premoveFromX = fromX;
7047                 premoveFromY = fromY;
7048                 premovePromoChar = promoChar;
7049                 gotPremove = 1;
7050                 if (appData.debugMode)
7051                     fprintf(debugFP, "Got premove: fromX %d,"
7052                             "fromY %d, toX %d, toY %d\n",
7053                             fromX, fromY, toX, toY);
7054             }
7055             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7056             return;
7057         }
7058         break;
7059
7060       case IcsPlayingWhite:
7061         /* User is moving for White */
7062         if (!WhiteOnMove(currentMove)) {
7063             if (!appData.premove) {
7064                 DisplayMoveError(_("It is Black's turn"));
7065             } else if (toX >= 0 && toY >= 0) {
7066                 premoveToX = toX;
7067                 premoveToY = toY;
7068                 premoveFromX = fromX;
7069                 premoveFromY = fromY;
7070                 premovePromoChar = promoChar;
7071                 gotPremove = 1;
7072                 if (appData.debugMode)
7073                     fprintf(debugFP, "Got premove: fromX %d,"
7074                             "fromY %d, toX %d, toY %d\n",
7075                             fromX, fromY, toX, toY);
7076             }
7077             DrawPosition(TRUE, boards[currentMove]);
7078             return;
7079         }
7080         break;
7081
7082       default:
7083         break;
7084
7085       case EditPosition:
7086         /* EditPosition, empty square, or different color piece;
7087            click-click move is possible */
7088         if (toX == -2 || toY == -2) {
7089             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7090             DrawPosition(FALSE, boards[currentMove]);
7091             return;
7092         } else if (toX >= 0 && toY >= 0) {
7093             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7094                 ChessSquare p = boards[0][rf][ff];
7095                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7096                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7097             }
7098             boards[0][toY][toX] = boards[0][fromY][fromX];
7099             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7100                 if(boards[0][fromY][0] != EmptySquare) {
7101                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7102                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7103                 }
7104             } else
7105             if(fromX == BOARD_RGHT+1) {
7106                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7107                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7108                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7109                 }
7110             } else
7111             boards[0][fromY][fromX] = gatingPiece;
7112             ClearHighlights();
7113             DrawPosition(FALSE, boards[currentMove]);
7114             return;
7115         }
7116         return;
7117     }
7118
7119     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7120     pup = boards[currentMove][toY][toX];
7121
7122     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7123     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7124          if( pup != EmptySquare ) return;
7125          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7126            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7127                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7128            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7129            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7130            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7131            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7132          fromY = DROP_RANK;
7133     }
7134
7135     /* [HGM] always test for legality, to get promotion info */
7136     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7137                                          fromY, fromX, toY, toX, promoChar);
7138
7139     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7140
7141     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7142
7143     /* [HGM] but possibly ignore an IllegalMove result */
7144     if (appData.testLegality) {
7145         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7146             DisplayMoveError(_("Illegal move"));
7147             return;
7148         }
7149     }
7150
7151     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7152         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7153              ClearPremoveHighlights(); // was included
7154         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7155         return;
7156     }
7157
7158     if(addToBookFlag) { // adding moves to book
7159         char buf[MSG_SIZ], move[MSG_SIZ];
7160         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7161         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7162                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7163         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7164         AddBookMove(buf);
7165         addToBookFlag = FALSE;
7166         ClearHighlights();
7167         return;
7168     }
7169
7170     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7171 }
7172
7173 /* Common tail of UserMoveEvent and DropMenuEvent */
7174 int
7175 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7176 {
7177     char *bookHit = 0;
7178
7179     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7180         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7181         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7182         if(WhiteOnMove(currentMove)) {
7183             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7184         } else {
7185             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7186         }
7187     }
7188
7189     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7190        move type in caller when we know the move is a legal promotion */
7191     if(moveType == NormalMove && promoChar)
7192         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7193
7194     /* [HGM] <popupFix> The following if has been moved here from
7195        UserMoveEvent(). Because it seemed to belong here (why not allow
7196        piece drops in training games?), and because it can only be
7197        performed after it is known to what we promote. */
7198     if (gameMode == Training) {
7199       /* compare the move played on the board to the next move in the
7200        * game. If they match, display the move and the opponent's response.
7201        * If they don't match, display an error message.
7202        */
7203       int saveAnimate;
7204       Board testBoard;
7205       CopyBoard(testBoard, boards[currentMove]);
7206       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7207
7208       if (CompareBoards(testBoard, boards[currentMove+1])) {
7209         ForwardInner(currentMove+1);
7210
7211         /* Autoplay the opponent's response.
7212          * if appData.animate was TRUE when Training mode was entered,
7213          * the response will be animated.
7214          */
7215         saveAnimate = appData.animate;
7216         appData.animate = animateTraining;
7217         ForwardInner(currentMove+1);
7218         appData.animate = saveAnimate;
7219
7220         /* check for the end of the game */
7221         if (currentMove >= forwardMostMove) {
7222           gameMode = PlayFromGameFile;
7223           ModeHighlight();
7224           SetTrainingModeOff();
7225           DisplayInformation(_("End of game"));
7226         }
7227       } else {
7228         DisplayError(_("Incorrect move"), 0);
7229       }
7230       return 1;
7231     }
7232
7233   /* Ok, now we know that the move is good, so we can kill
7234      the previous line in Analysis Mode */
7235   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7236                                 && currentMove < forwardMostMove) {
7237     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7238     else forwardMostMove = currentMove;
7239   }
7240
7241   ClearMap();
7242
7243   /* If we need the chess program but it's dead, restart it */
7244   ResurrectChessProgram();
7245
7246   /* A user move restarts a paused game*/
7247   if (pausing)
7248     PauseEvent();
7249
7250   thinkOutput[0] = NULLCHAR;
7251
7252   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7253
7254   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7255     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7256     return 1;
7257   }
7258
7259   if (gameMode == BeginningOfGame) {
7260     if (appData.noChessProgram) {
7261       gameMode = EditGame;
7262       SetGameInfo();
7263     } else {
7264       char buf[MSG_SIZ];
7265       gameMode = MachinePlaysBlack;
7266       StartClocks();
7267       SetGameInfo();
7268       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7269       DisplayTitle(buf);
7270       if (first.sendName) {
7271         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7272         SendToProgram(buf, &first);
7273       }
7274       StartClocks();
7275     }
7276     ModeHighlight();
7277   }
7278
7279   /* Relay move to ICS or chess engine */
7280   if (appData.icsActive) {
7281     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7282         gameMode == IcsExamining) {
7283       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7284         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7285         SendToICS("draw ");
7286         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7287       }
7288       // also send plain move, in case ICS does not understand atomic claims
7289       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7290       ics_user_moved = 1;
7291     }
7292   } else {
7293     if (first.sendTime && (gameMode == BeginningOfGame ||
7294                            gameMode == MachinePlaysWhite ||
7295                            gameMode == MachinePlaysBlack)) {
7296       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7297     }
7298     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7299          // [HGM] book: if program might be playing, let it use book
7300         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7301         first.maybeThinking = TRUE;
7302     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7303         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7304         SendBoard(&first, currentMove+1);
7305         if(second.analyzing) {
7306             if(!second.useSetboard) SendToProgram("undo\n", &second);
7307             SendBoard(&second, currentMove+1);
7308         }
7309     } else {
7310         SendMoveToProgram(forwardMostMove-1, &first);
7311         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7312     }
7313     if (currentMove == cmailOldMove + 1) {
7314       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7315     }
7316   }
7317
7318   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7319
7320   switch (gameMode) {
7321   case EditGame:
7322     if(appData.testLegality)
7323     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7324     case MT_NONE:
7325     case MT_CHECK:
7326       break;
7327     case MT_CHECKMATE:
7328     case MT_STAINMATE:
7329       if (WhiteOnMove(currentMove)) {
7330         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7331       } else {
7332         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7333       }
7334       break;
7335     case MT_STALEMATE:
7336       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7337       break;
7338     }
7339     break;
7340
7341   case MachinePlaysBlack:
7342   case MachinePlaysWhite:
7343     /* disable certain menu options while machine is thinking */
7344     SetMachineThinkingEnables();
7345     break;
7346
7347   default:
7348     break;
7349   }
7350
7351   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7352   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7353
7354   if(bookHit) { // [HGM] book: simulate book reply
7355         static char bookMove[MSG_SIZ]; // a bit generous?
7356
7357         programStats.nodes = programStats.depth = programStats.time =
7358         programStats.score = programStats.got_only_move = 0;
7359         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7360
7361         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7362         strcat(bookMove, bookHit);
7363         HandleMachineMove(bookMove, &first);
7364   }
7365   return 1;
7366 }
7367
7368 void
7369 MarkByFEN(char *fen)
7370 {
7371         int r, f;
7372         if(!appData.markers || !appData.highlightDragging) return;
7373         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7374         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7375         while(*fen) {
7376             int s = 0;
7377             marker[r][f] = 0;
7378             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7379             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7380             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7381             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7382             if(*fen == 'T') marker[r][f++] = 0; else
7383             if(*fen == 'Y') marker[r][f++] = 1; else
7384             if(*fen == 'G') marker[r][f++] = 3; else
7385             if(*fen == 'B') marker[r][f++] = 4; else
7386             if(*fen == 'C') marker[r][f++] = 5; else
7387             if(*fen == 'M') marker[r][f++] = 6; else
7388             if(*fen == 'W') marker[r][f++] = 7; else
7389             if(*fen == 'D') marker[r][f++] = 8; else
7390             if(*fen == 'R') marker[r][f++] = 2; else {
7391                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7392               f += s; fen -= s>0;
7393             }
7394             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7395             if(r < 0) break;
7396             fen++;
7397         }
7398         DrawPosition(TRUE, NULL);
7399 }
7400
7401 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7402
7403 void
7404 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7405 {
7406     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7407     Markers *m = (Markers *) closure;
7408     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7409         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7410                          || kind == WhiteCapturesEnPassant
7411                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7412     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7413 }
7414
7415 static int hoverSavedValid;
7416
7417 void
7418 MarkTargetSquares (int clear)
7419 {
7420   int x, y, sum=0;
7421   if(clear) { // no reason to ever suppress clearing
7422     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7423     hoverSavedValid = 0;
7424     if(!sum) return; // nothing was cleared,no redraw needed
7425   } else {
7426     int capt = 0;
7427     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7428        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7429     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7430     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7431       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7432       if(capt)
7433       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7434     }
7435   }
7436   DrawPosition(FALSE, NULL);
7437 }
7438
7439 int
7440 Explode (Board board, int fromX, int fromY, int toX, int toY)
7441 {
7442     if(gameInfo.variant == VariantAtomic &&
7443        (board[toY][toX] != EmptySquare ||                     // capture?
7444         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7445                          board[fromY][fromX] == BlackPawn   )
7446       )) {
7447         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7448         return TRUE;
7449     }
7450     return FALSE;
7451 }
7452
7453 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7454
7455 int
7456 CanPromote (ChessSquare piece, int y)
7457 {
7458         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7459         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7460         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7461         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7462            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7463           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7464            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7465         return (piece == BlackPawn && y <= zone ||
7466                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7467                 piece == BlackLance && y <= zone ||
7468                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7469 }
7470
7471 void
7472 HoverEvent (int xPix, int yPix, int x, int y)
7473 {
7474         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7475         int r, f;
7476         if(!first.highlight) return;
7477         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7478         if(x == oldX && y == oldY) return; // only do something if we enter new square
7479         oldFromX = fromX; oldFromY = fromY;
7480         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7481           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7482             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7483           hoverSavedValid = 1;
7484         } else if(oldX != x || oldY != y) {
7485           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7486           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7487           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7488             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7489           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7490             char buf[MSG_SIZ];
7491             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7492             SendToProgram(buf, &first);
7493           }
7494           oldX = x; oldY = y;
7495 //        SetHighlights(fromX, fromY, x, y);
7496         }
7497 }
7498
7499 void ReportClick(char *action, int x, int y)
7500 {
7501         char buf[MSG_SIZ]; // Inform engine of what user does
7502         int r, f;
7503         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7504           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7505             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7506         if(!first.highlight || gameMode == EditPosition) return;
7507         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7508         SendToProgram(buf, &first);
7509 }
7510
7511 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7512
7513 void
7514 LeftClick (ClickType clickType, int xPix, int yPix)
7515 {
7516     int x, y;
7517     Boolean saveAnimate;
7518     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7519     char promoChoice = NULLCHAR;
7520     ChessSquare piece;
7521     static TimeMark lastClickTime, prevClickTime;
7522
7523     x = EventToSquare(xPix, BOARD_WIDTH);
7524     y = EventToSquare(yPix, BOARD_HEIGHT);
7525     if (!flipView && y >= 0) {
7526         y = BOARD_HEIGHT - 1 - y;
7527     }
7528     if (flipView && x >= 0) {
7529         x = BOARD_WIDTH - 1 - x;
7530     }
7531
7532     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7533         static int dummy;
7534         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7535         right = TRUE;
7536         return;
7537     }
7538
7539     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7540
7541     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7542
7543     if (clickType == Press) ErrorPopDown();
7544     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7545
7546     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7547         defaultPromoChoice = promoSweep;
7548         promoSweep = EmptySquare;   // terminate sweep
7549         promoDefaultAltered = TRUE;
7550         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7551     }
7552
7553     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7554         if(clickType == Release) return; // ignore upclick of click-click destination
7555         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7556         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7557         if(gameInfo.holdingsWidth &&
7558                 (WhiteOnMove(currentMove)
7559                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7560                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7561             // click in right holdings, for determining promotion piece
7562             ChessSquare p = boards[currentMove][y][x];
7563             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7564             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7565             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7566                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7567                 fromX = fromY = -1;
7568                 return;
7569             }
7570         }
7571         DrawPosition(FALSE, boards[currentMove]);
7572         return;
7573     }
7574
7575     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7576     if(clickType == Press
7577             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7578               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7579               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7580         return;
7581
7582     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7583         // could be static click on premove from-square: abort premove
7584         gotPremove = 0;
7585         ClearPremoveHighlights();
7586     }
7587
7588     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7589         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7590
7591     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7592         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7593                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7594         defaultPromoChoice = DefaultPromoChoice(side);
7595     }
7596
7597     autoQueen = appData.alwaysPromoteToQueen;
7598
7599     if (fromX == -1) {
7600       int originalY = y;
7601       gatingPiece = EmptySquare;
7602       if (clickType != Press) {
7603         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7604             DragPieceEnd(xPix, yPix); dragging = 0;
7605             DrawPosition(FALSE, NULL);
7606         }
7607         return;
7608       }
7609       doubleClick = FALSE;
7610       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7611         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7612       }
7613       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7614       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7615          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7616          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7617             /* First square */
7618             if (OKToStartUserMove(fromX, fromY)) {
7619                 second = 0;
7620                 ReportClick("lift", x, y);
7621                 MarkTargetSquares(0);
7622                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7623                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7624                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7625                     promoSweep = defaultPromoChoice;
7626                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7627                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7628                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7629                 }
7630                 if (appData.highlightDragging) {
7631                     SetHighlights(fromX, fromY, -1, -1);
7632                 } else {
7633                     ClearHighlights();
7634                 }
7635             } else fromX = fromY = -1;
7636             return;
7637         }
7638     }
7639
7640     /* fromX != -1 */
7641     if (clickType == Press && gameMode != EditPosition) {
7642         ChessSquare fromP;
7643         ChessSquare toP;
7644         int frc;
7645
7646         // ignore off-board to clicks
7647         if(y < 0 || x < 0) return;
7648
7649         /* Check if clicking again on the same color piece */
7650         fromP = boards[currentMove][fromY][fromX];
7651         toP = boards[currentMove][y][x];
7652         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7653         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7654             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7655            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7656              WhitePawn <= toP && toP <= WhiteKing &&
7657              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7658              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7659             (BlackPawn <= fromP && fromP <= BlackKing &&
7660              BlackPawn <= toP && toP <= BlackKing &&
7661              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7662              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7663             /* Clicked again on same color piece -- changed his mind */
7664             second = (x == fromX && y == fromY);
7665             killX = killY = -1;
7666             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7667                 second = FALSE; // first double-click rather than scond click
7668                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7669             }
7670             promoDefaultAltered = FALSE;
7671             MarkTargetSquares(1);
7672            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7673             if (appData.highlightDragging) {
7674                 SetHighlights(x, y, -1, -1);
7675             } else {
7676                 ClearHighlights();
7677             }
7678             if (OKToStartUserMove(x, y)) {
7679                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7680                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7681                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7682                  gatingPiece = boards[currentMove][fromY][fromX];
7683                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7684                 fromX = x;
7685                 fromY = y; dragging = 1;
7686                 if(!second) ReportClick("lift", x, y);
7687                 MarkTargetSquares(0);
7688                 DragPieceBegin(xPix, yPix, FALSE);
7689                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7690                     promoSweep = defaultPromoChoice;
7691                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7692                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7693                 }
7694             }
7695            }
7696            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7697            second = FALSE;
7698         }
7699         // ignore clicks on holdings
7700         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7701     }
7702
7703     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7704         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7705         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7706         return;
7707     }
7708
7709     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7710         DragPieceEnd(xPix, yPix); dragging = 0;
7711         if(clearFlag) {
7712             // a deferred attempt to click-click move an empty square on top of a piece
7713             boards[currentMove][y][x] = EmptySquare;
7714             ClearHighlights();
7715             DrawPosition(FALSE, boards[currentMove]);
7716             fromX = fromY = -1; clearFlag = 0;
7717             return;
7718         }
7719         if (appData.animateDragging) {
7720             /* Undo animation damage if any */
7721             DrawPosition(FALSE, NULL);
7722         }
7723         if (second) {
7724             /* Second up/down in same square; just abort move */
7725             second = 0;
7726             fromX = fromY = -1;
7727             gatingPiece = EmptySquare;
7728             MarkTargetSquares(1);
7729             ClearHighlights();
7730             gotPremove = 0;
7731             ClearPremoveHighlights();
7732         } else {
7733             /* First upclick in same square; start click-click mode */
7734             SetHighlights(x, y, -1, -1);
7735         }
7736         return;
7737     }
7738
7739     clearFlag = 0;
7740
7741     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7742        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7743         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7744         DisplayMessage(_("only marked squares are legal"),"");
7745         DrawPosition(TRUE, NULL);
7746         return; // ignore to-click
7747     }
7748
7749     /* we now have a different from- and (possibly off-board) to-square */
7750     /* Completed move */
7751     if(!sweepSelecting) {
7752         toX = x;
7753         toY = y;
7754     }
7755
7756     piece = boards[currentMove][fromY][fromX];
7757
7758     saveAnimate = appData.animate;
7759     if (clickType == Press) {
7760         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7761         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7762             // must be Edit Position mode with empty-square selected
7763             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7764             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7765             return;
7766         }
7767         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7768             return;
7769         }
7770         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7771             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7772         } else
7773         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7774         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7775           if(appData.sweepSelect) {
7776             promoSweep = defaultPromoChoice;
7777             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7778             selectFlag = 0; lastX = xPix; lastY = yPix;
7779             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7780             Sweep(0); // Pawn that is going to promote: preview promotion piece
7781             sweepSelecting = 1;
7782             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7783             MarkTargetSquares(1);
7784           }
7785           return; // promo popup appears on up-click
7786         }
7787         /* Finish clickclick move */
7788         if (appData.animate || appData.highlightLastMove) {
7789             SetHighlights(fromX, fromY, toX, toY);
7790         } else {
7791             ClearHighlights();
7792         }
7793     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7794         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7795         *promoRestrict = 0;
7796         if (appData.animate || appData.highlightLastMove) {
7797             SetHighlights(fromX, fromY, toX, toY);
7798         } else {
7799             ClearHighlights();
7800         }
7801     } else {
7802 #if 0
7803 // [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
7804         /* Finish drag move */
7805         if (appData.highlightLastMove) {
7806             SetHighlights(fromX, fromY, toX, toY);
7807         } else {
7808             ClearHighlights();
7809         }
7810 #endif
7811         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7812           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7813         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7814         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7815           dragging *= 2;            // flag button-less dragging if we are dragging
7816           MarkTargetSquares(1);
7817           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7818           else {
7819             kill2X = killX; kill2Y = killY;
7820             killX = x; killY = y;     //remeber this square as intermediate
7821             ReportClick("put", x, y); // and inform engine
7822             ReportClick("lift", x, y);
7823             MarkTargetSquares(0);
7824             return;
7825           }
7826         }
7827         DragPieceEnd(xPix, yPix); dragging = 0;
7828         /* Don't animate move and drag both */
7829         appData.animate = FALSE;
7830     }
7831
7832     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7833     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7834         ChessSquare piece = boards[currentMove][fromY][fromX];
7835         if(gameMode == EditPosition && piece != EmptySquare &&
7836            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7837             int n;
7838
7839             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7840                 n = PieceToNumber(piece - (int)BlackPawn);
7841                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7842                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7843                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7844             } else
7845             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7846                 n = PieceToNumber(piece);
7847                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7848                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7849                 boards[currentMove][n][BOARD_WIDTH-2]++;
7850             }
7851             boards[currentMove][fromY][fromX] = EmptySquare;
7852         }
7853         ClearHighlights();
7854         fromX = fromY = -1;
7855         MarkTargetSquares(1);
7856         DrawPosition(TRUE, boards[currentMove]);
7857         return;
7858     }
7859
7860     // off-board moves should not be highlighted
7861     if(x < 0 || y < 0) ClearHighlights();
7862     else ReportClick("put", x, y);
7863
7864     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7865
7866     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7867
7868     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7869         SetHighlights(fromX, fromY, toX, toY);
7870         MarkTargetSquares(1);
7871         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7872             // [HGM] super: promotion to captured piece selected from holdings
7873             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7874             promotionChoice = TRUE;
7875             // kludge follows to temporarily execute move on display, without promoting yet
7876             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7877             boards[currentMove][toY][toX] = p;
7878             DrawPosition(FALSE, boards[currentMove]);
7879             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7880             boards[currentMove][toY][toX] = q;
7881             DisplayMessage("Click in holdings to choose piece", "");
7882             return;
7883         }
7884         PromotionPopUp(promoChoice);
7885     } else {
7886         int oldMove = currentMove;
7887         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7888         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7889         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7890         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7891            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7892             DrawPosition(TRUE, boards[currentMove]);
7893         MarkTargetSquares(1);
7894         fromX = fromY = -1;
7895     }
7896     appData.animate = saveAnimate;
7897     if (appData.animate || appData.animateDragging) {
7898         /* Undo animation damage if needed */
7899         DrawPosition(FALSE, NULL);
7900     }
7901 }
7902
7903 int
7904 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7905 {   // front-end-free part taken out of PieceMenuPopup
7906     int whichMenu; int xSqr, ySqr;
7907
7908     if(seekGraphUp) { // [HGM] seekgraph
7909         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7910         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7911         return -2;
7912     }
7913
7914     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7915          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7916         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7917         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7918         if(action == Press)   {
7919             originalFlip = flipView;
7920             flipView = !flipView; // temporarily flip board to see game from partners perspective
7921             DrawPosition(TRUE, partnerBoard);
7922             DisplayMessage(partnerStatus, "");
7923             partnerUp = TRUE;
7924         } else if(action == Release) {
7925             flipView = originalFlip;
7926             DrawPosition(TRUE, boards[currentMove]);
7927             partnerUp = FALSE;
7928         }
7929         return -2;
7930     }
7931
7932     xSqr = EventToSquare(x, BOARD_WIDTH);
7933     ySqr = EventToSquare(y, BOARD_HEIGHT);
7934     if (action == Release) {
7935         if(pieceSweep != EmptySquare) {
7936             EditPositionMenuEvent(pieceSweep, toX, toY);
7937             pieceSweep = EmptySquare;
7938         } else UnLoadPV(); // [HGM] pv
7939     }
7940     if (action != Press) return -2; // return code to be ignored
7941     switch (gameMode) {
7942       case IcsExamining:
7943         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7944       case EditPosition:
7945         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7946         if (xSqr < 0 || ySqr < 0) return -1;
7947         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7948         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7949         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7950         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7951         NextPiece(0);
7952         return 2; // grab
7953       case IcsObserving:
7954         if(!appData.icsEngineAnalyze) return -1;
7955       case IcsPlayingWhite:
7956       case IcsPlayingBlack:
7957         if(!appData.zippyPlay) goto noZip;
7958       case AnalyzeMode:
7959       case AnalyzeFile:
7960       case MachinePlaysWhite:
7961       case MachinePlaysBlack:
7962       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7963         if (!appData.dropMenu) {
7964           LoadPV(x, y);
7965           return 2; // flag front-end to grab mouse events
7966         }
7967         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7968            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7969       case EditGame:
7970       noZip:
7971         if (xSqr < 0 || ySqr < 0) return -1;
7972         if (!appData.dropMenu || appData.testLegality &&
7973             gameInfo.variant != VariantBughouse &&
7974             gameInfo.variant != VariantCrazyhouse) return -1;
7975         whichMenu = 1; // drop menu
7976         break;
7977       default:
7978         return -1;
7979     }
7980
7981     if (((*fromX = xSqr) < 0) ||
7982         ((*fromY = ySqr) < 0)) {
7983         *fromX = *fromY = -1;
7984         return -1;
7985     }
7986     if (flipView)
7987       *fromX = BOARD_WIDTH - 1 - *fromX;
7988     else
7989       *fromY = BOARD_HEIGHT - 1 - *fromY;
7990
7991     return whichMenu;
7992 }
7993
7994 void
7995 Wheel (int dir, int x, int y)
7996 {
7997     if(gameMode == EditPosition) {
7998         int xSqr = EventToSquare(x, BOARD_WIDTH);
7999         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8000         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8001         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8002         do {
8003             boards[currentMove][ySqr][xSqr] += dir;
8004             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8005             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8006         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8007         DrawPosition(FALSE, boards[currentMove]);
8008     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8009 }
8010
8011 void
8012 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8013 {
8014 //    char * hint = lastHint;
8015     FrontEndProgramStats stats;
8016
8017     stats.which = cps == &first ? 0 : 1;
8018     stats.depth = cpstats->depth;
8019     stats.nodes = cpstats->nodes;
8020     stats.score = cpstats->score;
8021     stats.time = cpstats->time;
8022     stats.pv = cpstats->movelist;
8023     stats.hint = lastHint;
8024     stats.an_move_index = 0;
8025     stats.an_move_count = 0;
8026
8027     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8028         stats.hint = cpstats->move_name;
8029         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8030         stats.an_move_count = cpstats->nr_moves;
8031     }
8032
8033     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
8034
8035     SetProgramStats( &stats );
8036 }
8037
8038 void
8039 ClearEngineOutputPane (int which)
8040 {
8041     static FrontEndProgramStats dummyStats;
8042     dummyStats.which = which;
8043     dummyStats.pv = "#";
8044     SetProgramStats( &dummyStats );
8045 }
8046
8047 #define MAXPLAYERS 500
8048
8049 char *
8050 TourneyStandings (int display)
8051 {
8052     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8053     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8054     char result, *p, *names[MAXPLAYERS];
8055
8056     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8057         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8058     names[0] = p = strdup(appData.participants);
8059     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8060
8061     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8062
8063     while(result = appData.results[nr]) {
8064         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8065         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8066         wScore = bScore = 0;
8067         switch(result) {
8068           case '+': wScore = 2; break;
8069           case '-': bScore = 2; break;
8070           case '=': wScore = bScore = 1; break;
8071           case ' ':
8072           case '*': return strdup("busy"); // tourney not finished
8073         }
8074         score[w] += wScore;
8075         score[b] += bScore;
8076         games[w]++;
8077         games[b]++;
8078         nr++;
8079     }
8080     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8081     for(w=0; w<nPlayers; w++) {
8082         bScore = -1;
8083         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8084         ranking[w] = b; points[w] = bScore; score[b] = -2;
8085     }
8086     p = malloc(nPlayers*34+1);
8087     for(w=0; w<nPlayers && w<display; w++)
8088         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8089     free(names[0]);
8090     return p;
8091 }
8092
8093 void
8094 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8095 {       // count all piece types
8096         int p, f, r;
8097         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8098         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8099         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8100                 p = board[r][f];
8101                 pCnt[p]++;
8102                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8103                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8104                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8105                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8106                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8107                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8108         }
8109 }
8110
8111 int
8112 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8113 {
8114         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8115         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8116
8117         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8118         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8119         if(myPawns == 2 && nMine == 3) // KPP
8120             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8121         if(myPawns == 1 && nMine == 2) // KP
8122             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8123         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8124             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8125         if(myPawns) return FALSE;
8126         if(pCnt[WhiteRook+side])
8127             return pCnt[BlackRook-side] ||
8128                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8129                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8130                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8131         if(pCnt[WhiteCannon+side]) {
8132             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8133             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8134         }
8135         if(pCnt[WhiteKnight+side])
8136             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8137         return FALSE;
8138 }
8139
8140 int
8141 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8142 {
8143         VariantClass v = gameInfo.variant;
8144
8145         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8146         if(v == VariantShatranj) return TRUE; // always winnable through baring
8147         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8148         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8149
8150         if(v == VariantXiangqi) {
8151                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8152
8153                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8154                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8155                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8156                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8157                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8158                 if(stale) // we have at least one last-rank P plus perhaps C
8159                     return majors // KPKX
8160                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8161                 else // KCA*E*
8162                     return pCnt[WhiteFerz+side] // KCAK
8163                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8164                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8165                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8166
8167         } else if(v == VariantKnightmate) {
8168                 if(nMine == 1) return FALSE;
8169                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8170         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8171                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8172
8173                 if(nMine == 1) return FALSE; // bare King
8174                 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
8175                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8176                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8177                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8178                 if(pCnt[WhiteKnight+side])
8179                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8180                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8181                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8182                 if(nBishops)
8183                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8184                 if(pCnt[WhiteAlfil+side])
8185                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8186                 if(pCnt[WhiteWazir+side])
8187                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8188         }
8189
8190         return TRUE;
8191 }
8192
8193 int
8194 CompareWithRights (Board b1, Board b2)
8195 {
8196     int rights = 0;
8197     if(!CompareBoards(b1, b2)) return FALSE;
8198     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8199     /* compare castling rights */
8200     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8201            rights++; /* King lost rights, while rook still had them */
8202     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8203         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8204            rights++; /* but at least one rook lost them */
8205     }
8206     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8207            rights++;
8208     if( b1[CASTLING][5] != NoRights ) {
8209         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8210            rights++;
8211     }
8212     return rights == 0;
8213 }
8214
8215 int
8216 Adjudicate (ChessProgramState *cps)
8217 {       // [HGM] some adjudications useful with buggy engines
8218         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8219         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8220         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8221         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8222         int k, drop, count = 0; static int bare = 1;
8223         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8224         Boolean canAdjudicate = !appData.icsActive;
8225
8226         // most tests only when we understand the game, i.e. legality-checking on
8227             if( appData.testLegality )
8228             {   /* [HGM] Some more adjudications for obstinate engines */
8229                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8230                 static int moveCount = 6;
8231                 ChessMove result;
8232                 char *reason = NULL;
8233
8234                 /* Count what is on board. */
8235                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8236
8237                 /* Some material-based adjudications that have to be made before stalemate test */
8238                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8239                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8240                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8241                      if(canAdjudicate && appData.checkMates) {
8242                          if(engineOpponent)
8243                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8244                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8245                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8246                          return 1;
8247                      }
8248                 }
8249
8250                 /* Bare King in Shatranj (loses) or Losers (wins) */
8251                 if( nrW == 1 || nrB == 1) {
8252                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8253                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8254                      if(canAdjudicate && appData.checkMates) {
8255                          if(engineOpponent)
8256                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8257                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8258                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8259                          return 1;
8260                      }
8261                   } else
8262                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8263                   {    /* bare King */
8264                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8265                         if(canAdjudicate && appData.checkMates) {
8266                             /* but only adjudicate if adjudication enabled */
8267                             if(engineOpponent)
8268                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8269                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8270                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8271                             return 1;
8272                         }
8273                   }
8274                 } else bare = 1;
8275
8276
8277             // don't wait for engine to announce game end if we can judge ourselves
8278             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8279               case MT_CHECK:
8280                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8281                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8282                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8283                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8284                             checkCnt++;
8285                         if(checkCnt >= 2) {
8286                             reason = "Xboard adjudication: 3rd check";
8287                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8288                             break;
8289                         }
8290                     }
8291                 }
8292               case MT_NONE:
8293               default:
8294                 break;
8295               case MT_STEALMATE:
8296               case MT_STALEMATE:
8297               case MT_STAINMATE:
8298                 reason = "Xboard adjudication: Stalemate";
8299                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8300                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8301                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8302                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8303                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8304                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8305                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8306                                                                         EP_CHECKMATE : EP_WINS);
8307                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8308                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8309                 }
8310                 break;
8311               case MT_CHECKMATE:
8312                 reason = "Xboard adjudication: Checkmate";
8313                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8314                 if(gameInfo.variant == VariantShogi) {
8315                     if(forwardMostMove > backwardMostMove
8316                        && moveList[forwardMostMove-1][1] == '@'
8317                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8318                         reason = "XBoard adjudication: pawn-drop mate";
8319                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8320                     }
8321                 }
8322                 break;
8323             }
8324
8325                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8326                     case EP_STALEMATE:
8327                         result = GameIsDrawn; break;
8328                     case EP_CHECKMATE:
8329                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8330                     case EP_WINS:
8331                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8332                     default:
8333                         result = EndOfFile;
8334                 }
8335                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8336                     if(engineOpponent)
8337                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8338                     GameEnds( result, reason, GE_XBOARD );
8339                     return 1;
8340                 }
8341
8342                 /* Next absolutely insufficient mating material. */
8343                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8344                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8345                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8346
8347                      /* always flag draws, for judging claims */
8348                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8349
8350                      if(canAdjudicate && appData.materialDraws) {
8351                          /* but only adjudicate them if adjudication enabled */
8352                          if(engineOpponent) {
8353                            SendToProgram("force\n", engineOpponent); // suppress reply
8354                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8355                          }
8356                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8357                          return 1;
8358                      }
8359                 }
8360
8361                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8362                 if(gameInfo.variant == VariantXiangqi ?
8363                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8364                  : nrW + nrB == 4 &&
8365                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8366                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8367                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8368                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8369                    ) ) {
8370                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8371                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8372                           if(engineOpponent) {
8373                             SendToProgram("force\n", engineOpponent); // suppress reply
8374                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8375                           }
8376                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8377                           return 1;
8378                      }
8379                 } else moveCount = 6;
8380             }
8381
8382         // Repetition draws and 50-move rule can be applied independently of legality testing
8383
8384                 /* Check for rep-draws */
8385                 count = 0;
8386                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8387                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8388                 for(k = forwardMostMove-2;
8389                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8390                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8391                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8392                     k-=2)
8393                 {   int rights=0;
8394                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8395                         /* compare castling rights */
8396                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8397                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8398                                 rights++; /* King lost rights, while rook still had them */
8399                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8400                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8401                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8402                                    rights++; /* but at least one rook lost them */
8403                         }
8404                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8405                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8406                                 rights++;
8407                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8408                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8409                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8410                                    rights++;
8411                         }
8412                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8413                             && appData.drawRepeats > 1) {
8414                              /* adjudicate after user-specified nr of repeats */
8415                              int result = GameIsDrawn;
8416                              char *details = "XBoard adjudication: repetition draw";
8417                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8418                                 // [HGM] xiangqi: check for forbidden perpetuals
8419                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8420                                 for(m=forwardMostMove; m>k; m-=2) {
8421                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8422                                         ourPerpetual = 0; // the current mover did not always check
8423                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8424                                         hisPerpetual = 0; // the opponent did not always check
8425                                 }
8426                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8427                                                                         ourPerpetual, hisPerpetual);
8428                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8429                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8430                                     details = "Xboard adjudication: perpetual checking";
8431                                 } else
8432                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8433                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8434                                 } else
8435                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8436                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8437                                         result = BlackWins;
8438                                         details = "Xboard adjudication: repetition";
8439                                     }
8440                                 } else // it must be XQ
8441                                 // Now check for perpetual chases
8442                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8443                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8444                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8445                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8446                                         static char resdet[MSG_SIZ];
8447                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8448                                         details = resdet;
8449                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8450                                     } else
8451                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8452                                         break; // Abort repetition-checking loop.
8453                                 }
8454                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8455                              }
8456                              if(engineOpponent) {
8457                                SendToProgram("force\n", engineOpponent); // suppress reply
8458                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8459                              }
8460                              GameEnds( result, details, GE_XBOARD );
8461                              return 1;
8462                         }
8463                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8464                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8465                     }
8466                 }
8467
8468                 /* Now we test for 50-move draws. Determine ply count */
8469                 count = forwardMostMove;
8470                 /* look for last irreversble move */
8471                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8472                     count--;
8473                 /* if we hit starting position, add initial plies */
8474                 if( count == backwardMostMove )
8475                     count -= initialRulePlies;
8476                 count = forwardMostMove - count;
8477                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8478                         // adjust reversible move counter for checks in Xiangqi
8479                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8480                         if(i < backwardMostMove) i = backwardMostMove;
8481                         while(i <= forwardMostMove) {
8482                                 lastCheck = inCheck; // check evasion does not count
8483                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8484                                 if(inCheck || lastCheck) count--; // check does not count
8485                                 i++;
8486                         }
8487                 }
8488                 if( count >= 100)
8489                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8490                          /* this is used to judge if draw claims are legal */
8491                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8492                          if(engineOpponent) {
8493                            SendToProgram("force\n", engineOpponent); // suppress reply
8494                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8495                          }
8496                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8497                          return 1;
8498                 }
8499
8500                 /* if draw offer is pending, treat it as a draw claim
8501                  * when draw condition present, to allow engines a way to
8502                  * claim draws before making their move to avoid a race
8503                  * condition occurring after their move
8504                  */
8505                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8506                          char *p = NULL;
8507                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8508                              p = "Draw claim: 50-move rule";
8509                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8510                              p = "Draw claim: 3-fold repetition";
8511                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8512                              p = "Draw claim: insufficient mating material";
8513                          if( p != NULL && canAdjudicate) {
8514                              if(engineOpponent) {
8515                                SendToProgram("force\n", engineOpponent); // suppress reply
8516                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8517                              }
8518                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8519                              return 1;
8520                          }
8521                 }
8522
8523                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8524                     if(engineOpponent) {
8525                       SendToProgram("force\n", engineOpponent); // suppress reply
8526                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8527                     }
8528                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8529                     return 1;
8530                 }
8531         return 0;
8532 }
8533
8534 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8535 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8536 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8537
8538 static int
8539 BitbaseProbe ()
8540 {
8541     int pieces[10], squares[10], cnt=0, r, f, res;
8542     static int loaded;
8543     static PPROBE_EGBB probeBB;
8544     if(!appData.testLegality) return 10;
8545     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8546     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8547     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8548     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8549         ChessSquare piece = boards[forwardMostMove][r][f];
8550         int black = (piece >= BlackPawn);
8551         int type = piece - black*BlackPawn;
8552         if(piece == EmptySquare) continue;
8553         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8554         if(type == WhiteKing) type = WhiteQueen + 1;
8555         type = egbbCode[type];
8556         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8557         pieces[cnt] = type + black*6;
8558         if(++cnt > 5) return 11;
8559     }
8560     pieces[cnt] = squares[cnt] = 0;
8561     // probe EGBB
8562     if(loaded == 2) return 13; // loading failed before
8563     if(loaded == 0) {
8564         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8565         HMODULE lib;
8566         PLOAD_EGBB loadBB;
8567         loaded = 2; // prepare for failure
8568         if(!path) return 13; // no egbb installed
8569         strncpy(buf, path + 8, MSG_SIZ);
8570         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8571         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8572         lib = LoadLibrary(buf);
8573         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8574         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8575         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8576         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8577         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8578         loaded = 1; // success!
8579     }
8580     res = probeBB(forwardMostMove & 1, pieces, squares);
8581     return res > 0 ? 1 : res < 0 ? -1 : 0;
8582 }
8583
8584 char *
8585 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8586 {   // [HGM] book: this routine intercepts moves to simulate book replies
8587     char *bookHit = NULL;
8588
8589     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8590         char buf[MSG_SIZ];
8591         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8592         SendToProgram(buf, cps);
8593     }
8594     //first determine if the incoming move brings opponent into his book
8595     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8596         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8597     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8598     if(bookHit != NULL && !cps->bookSuspend) {
8599         // make sure opponent is not going to reply after receiving move to book position
8600         SendToProgram("force\n", cps);
8601         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8602     }
8603     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8604     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8605     // now arrange restart after book miss
8606     if(bookHit) {
8607         // after a book hit we never send 'go', and the code after the call to this routine
8608         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8609         char buf[MSG_SIZ], *move = bookHit;
8610         if(cps->useSAN) {
8611             int fromX, fromY, toX, toY;
8612             char promoChar;
8613             ChessMove moveType;
8614             move = buf + 30;
8615             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8616                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8617                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8618                                     PosFlags(forwardMostMove),
8619                                     fromY, fromX, toY, toX, promoChar, move);
8620             } else {
8621                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8622                 bookHit = NULL;
8623             }
8624         }
8625         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8626         SendToProgram(buf, cps);
8627         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8628     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8629         SendToProgram("go\n", cps);
8630         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8631     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8632         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8633             SendToProgram("go\n", cps);
8634         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8635     }
8636     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8637 }
8638
8639 int
8640 LoadError (char *errmess, ChessProgramState *cps)
8641 {   // unloads engine and switches back to -ncp mode if it was first
8642     if(cps->initDone) return FALSE;
8643     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8644     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8645     cps->pr = NoProc;
8646     if(cps == &first) {
8647         appData.noChessProgram = TRUE;
8648         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8649         gameMode = BeginningOfGame; ModeHighlight();
8650         SetNCPMode();
8651     }
8652     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8653     DisplayMessage("", ""); // erase waiting message
8654     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8655     return TRUE;
8656 }
8657
8658 char *savedMessage;
8659 ChessProgramState *savedState;
8660 void
8661 DeferredBookMove (void)
8662 {
8663         if(savedState->lastPing != savedState->lastPong)
8664                     ScheduleDelayedEvent(DeferredBookMove, 10);
8665         else
8666         HandleMachineMove(savedMessage, savedState);
8667 }
8668
8669 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8670 static ChessProgramState *stalledEngine;
8671 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8672
8673 void
8674 HandleMachineMove (char *message, ChessProgramState *cps)
8675 {
8676     static char firstLeg[20];
8677     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8678     char realname[MSG_SIZ];
8679     int fromX, fromY, toX, toY;
8680     ChessMove moveType;
8681     char promoChar, roar;
8682     char *p, *pv=buf1;
8683     int oldError;
8684     char *bookHit;
8685
8686     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8687         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8688         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8689             DisplayError(_("Invalid pairing from pairing engine"), 0);
8690             return;
8691         }
8692         pairingReceived = 1;
8693         NextMatchGame();
8694         return; // Skim the pairing messages here.
8695     }
8696
8697     oldError = cps->userError; cps->userError = 0;
8698
8699 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8700     /*
8701      * Kludge to ignore BEL characters
8702      */
8703     while (*message == '\007') message++;
8704
8705     /*
8706      * [HGM] engine debug message: ignore lines starting with '#' character
8707      */
8708     if(cps->debug && *message == '#') return;
8709
8710     /*
8711      * Look for book output
8712      */
8713     if (cps == &first && bookRequested) {
8714         if (message[0] == '\t' || message[0] == ' ') {
8715             /* Part of the book output is here; append it */
8716             strcat(bookOutput, message);
8717             strcat(bookOutput, "  \n");
8718             return;
8719         } else if (bookOutput[0] != NULLCHAR) {
8720             /* All of book output has arrived; display it */
8721             char *p = bookOutput;
8722             while (*p != NULLCHAR) {
8723                 if (*p == '\t') *p = ' ';
8724                 p++;
8725             }
8726             DisplayInformation(bookOutput);
8727             bookRequested = FALSE;
8728             /* Fall through to parse the current output */
8729         }
8730     }
8731
8732     /*
8733      * Look for machine move.
8734      */
8735     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8736         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8737     {
8738         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8739             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8740             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8741             stalledEngine = cps;
8742             if(appData.ponderNextMove) { // bring opponent out of ponder
8743                 if(gameMode == TwoMachinesPlay) {
8744                     if(cps->other->pause)
8745                         PauseEngine(cps->other);
8746                     else
8747                         SendToProgram("easy\n", cps->other);
8748                 }
8749             }
8750             StopClocks();
8751             return;
8752         }
8753
8754       if(cps->usePing) {
8755
8756         /* This method is only useful on engines that support ping */
8757         if(abortEngineThink) {
8758             if (appData.debugMode) {
8759                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8760             }
8761             SendToProgram("undo\n", cps);
8762             return;
8763         }
8764
8765         if (cps->lastPing != cps->lastPong) {
8766             /* Extra move from before last new; ignore */
8767             if (appData.debugMode) {
8768                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8769             }
8770           return;
8771         }
8772
8773       } else {
8774
8775         int machineWhite = FALSE;
8776
8777         switch (gameMode) {
8778           case BeginningOfGame:
8779             /* Extra move from before last reset; ignore */
8780             if (appData.debugMode) {
8781                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8782             }
8783             return;
8784
8785           case EndOfGame:
8786           case IcsIdle:
8787           default:
8788             /* Extra move after we tried to stop.  The mode test is
8789                not a reliable way of detecting this problem, but it's
8790                the best we can do on engines that don't support ping.
8791             */
8792             if (appData.debugMode) {
8793                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8794                         cps->which, gameMode);
8795             }
8796             SendToProgram("undo\n", cps);
8797             return;
8798
8799           case MachinePlaysWhite:
8800           case IcsPlayingWhite:
8801             machineWhite = TRUE;
8802             break;
8803
8804           case MachinePlaysBlack:
8805           case IcsPlayingBlack:
8806             machineWhite = FALSE;
8807             break;
8808
8809           case TwoMachinesPlay:
8810             machineWhite = (cps->twoMachinesColor[0] == 'w');
8811             break;
8812         }
8813         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8814             if (appData.debugMode) {
8815                 fprintf(debugFP,
8816                         "Ignoring move out of turn by %s, gameMode %d"
8817                         ", forwardMost %d\n",
8818                         cps->which, gameMode, forwardMostMove);
8819             }
8820             return;
8821         }
8822       }
8823
8824         if(cps->alphaRank) AlphaRank(machineMove, 4);
8825
8826         // [HGM] lion: (some very limited) support for Alien protocol
8827         killX = killY = kill2X = kill2Y = -1;
8828         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8829             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8830             return;
8831         }
8832         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8833             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8834             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8835         }
8836         if(firstLeg[0]) { // there was a previous leg;
8837             // only support case where same piece makes two step
8838             char buf[20], *p = machineMove+1, *q = buf+1, f;
8839             safeStrCpy(buf, machineMove, 20);
8840             while(isdigit(*q)) q++; // find start of to-square
8841             safeStrCpy(machineMove, firstLeg, 20);
8842             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8843             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8844             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8845             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8846             firstLeg[0] = NULLCHAR;
8847         }
8848
8849         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8850                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8851             /* Machine move could not be parsed; ignore it. */
8852           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8853                     machineMove, _(cps->which));
8854             DisplayMoveError(buf1);
8855             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8856                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8857             if (gameMode == TwoMachinesPlay) {
8858               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8859                        buf1, GE_XBOARD);
8860             }
8861             return;
8862         }
8863
8864         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8865         /* So we have to redo legality test with true e.p. status here,  */
8866         /* to make sure an illegal e.p. capture does not slip through,   */
8867         /* to cause a forfeit on a justified illegal-move complaint      */
8868         /* of the opponent.                                              */
8869         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8870            ChessMove moveType;
8871            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8872                              fromY, fromX, toY, toX, promoChar);
8873             if(moveType == IllegalMove) {
8874               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8875                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8876                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8877                            buf1, GE_XBOARD);
8878                 return;
8879            } else if(!appData.fischerCastling)
8880            /* [HGM] Kludge to handle engines that send FRC-style castling
8881               when they shouldn't (like TSCP-Gothic) */
8882            switch(moveType) {
8883              case WhiteASideCastleFR:
8884              case BlackASideCastleFR:
8885                toX+=2;
8886                currentMoveString[2]++;
8887                break;
8888              case WhiteHSideCastleFR:
8889              case BlackHSideCastleFR:
8890                toX--;
8891                currentMoveString[2]--;
8892                break;
8893              default: ; // nothing to do, but suppresses warning of pedantic compilers
8894            }
8895         }
8896         hintRequested = FALSE;
8897         lastHint[0] = NULLCHAR;
8898         bookRequested = FALSE;
8899         /* Program may be pondering now */
8900         cps->maybeThinking = TRUE;
8901         if (cps->sendTime == 2) cps->sendTime = 1;
8902         if (cps->offeredDraw) cps->offeredDraw--;
8903
8904         /* [AS] Save move info*/
8905         pvInfoList[ forwardMostMove ].score = programStats.score;
8906         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8907         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8908
8909         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8910
8911         /* Test suites abort the 'game' after one move */
8912         if(*appData.finger) {
8913            static FILE *f;
8914            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8915            if(!f) f = fopen(appData.finger, "w");
8916            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8917            else { DisplayFatalError("Bad output file", errno, 0); return; }
8918            free(fen);
8919            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8920         }
8921         if(appData.epd) {
8922            if(solvingTime >= 0) {
8923               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8924               totalTime += solvingTime; first.matchWins++;
8925            } else {
8926               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8927               second.matchWins++;
8928            }
8929            OutputKibitz(2, buf1);
8930            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8931         }
8932
8933         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8934         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8935             int count = 0;
8936
8937             while( count < adjudicateLossPlies ) {
8938                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8939
8940                 if( count & 1 ) {
8941                     score = -score; /* Flip score for winning side */
8942                 }
8943
8944                 if( score > appData.adjudicateLossThreshold ) {
8945                     break;
8946                 }
8947
8948                 count++;
8949             }
8950
8951             if( count >= adjudicateLossPlies ) {
8952                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8953
8954                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8955                     "Xboard adjudication",
8956                     GE_XBOARD );
8957
8958                 return;
8959             }
8960         }
8961
8962         if(Adjudicate(cps)) {
8963             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8964             return; // [HGM] adjudicate: for all automatic game ends
8965         }
8966
8967 #if ZIPPY
8968         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8969             first.initDone) {
8970           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8971                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8972                 SendToICS("draw ");
8973                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8974           }
8975           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8976           ics_user_moved = 1;
8977           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8978                 char buf[3*MSG_SIZ];
8979
8980                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8981                         programStats.score / 100.,
8982                         programStats.depth,
8983                         programStats.time / 100.,
8984                         (unsigned int)programStats.nodes,
8985                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8986                         programStats.movelist);
8987                 SendToICS(buf);
8988           }
8989         }
8990 #endif
8991
8992         /* [AS] Clear stats for next move */
8993         ClearProgramStats();
8994         thinkOutput[0] = NULLCHAR;
8995         hiddenThinkOutputState = 0;
8996
8997         bookHit = NULL;
8998         if (gameMode == TwoMachinesPlay) {
8999             /* [HGM] relaying draw offers moved to after reception of move */
9000             /* and interpreting offer as claim if it brings draw condition */
9001             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9002                 SendToProgram("draw\n", cps->other);
9003             }
9004             if (cps->other->sendTime) {
9005                 SendTimeRemaining(cps->other,
9006                                   cps->other->twoMachinesColor[0] == 'w');
9007             }
9008             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9009             if (firstMove && !bookHit) {
9010                 firstMove = FALSE;
9011                 if (cps->other->useColors) {
9012                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9013                 }
9014                 SendToProgram("go\n", cps->other);
9015             }
9016             cps->other->maybeThinking = TRUE;
9017         }
9018
9019         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9020
9021         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9022
9023         if (!pausing && appData.ringBellAfterMoves) {
9024             if(!roar) RingBell();
9025         }
9026
9027         /*
9028          * Reenable menu items that were disabled while
9029          * machine was thinking
9030          */
9031         if (gameMode != TwoMachinesPlay)
9032             SetUserThinkingEnables();
9033
9034         // [HGM] book: after book hit opponent has received move and is now in force mode
9035         // force the book reply into it, and then fake that it outputted this move by jumping
9036         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9037         if(bookHit) {
9038                 static char bookMove[MSG_SIZ]; // a bit generous?
9039
9040                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9041                 strcat(bookMove, bookHit);
9042                 message = bookMove;
9043                 cps = cps->other;
9044                 programStats.nodes = programStats.depth = programStats.time =
9045                 programStats.score = programStats.got_only_move = 0;
9046                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9047
9048                 if(cps->lastPing != cps->lastPong) {
9049                     savedMessage = message; // args for deferred call
9050                     savedState = cps;
9051                     ScheduleDelayedEvent(DeferredBookMove, 10);
9052                     return;
9053                 }
9054                 goto FakeBookMove;
9055         }
9056
9057         return;
9058     }
9059
9060     /* Set special modes for chess engines.  Later something general
9061      *  could be added here; for now there is just one kludge feature,
9062      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9063      *  when "xboard" is given as an interactive command.
9064      */
9065     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9066         cps->useSigint = FALSE;
9067         cps->useSigterm = FALSE;
9068     }
9069     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9070       ParseFeatures(message+8, cps);
9071       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9072     }
9073
9074     if (!strncmp(message, "setup ", 6) && 
9075         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9076           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9077                                         ) { // [HGM] allow first engine to define opening position
9078       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9079       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9080       *buf = NULLCHAR;
9081       if(sscanf(message, "setup (%s", buf) == 1) {
9082         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9083         ASSIGN(appData.pieceToCharTable, buf);
9084       }
9085       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9086       if(dummy >= 3) {
9087         while(message[s] && message[s++] != ' ');
9088         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9089            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9090             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9091             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9092           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9093           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9094           startedFromSetupPosition = FALSE;
9095         }
9096       }
9097       if(startedFromSetupPosition) return;
9098       ParseFEN(boards[0], &dummy, message+s, FALSE);
9099       DrawPosition(TRUE, boards[0]);
9100       CopyBoard(initialPosition, boards[0]);
9101       startedFromSetupPosition = TRUE;
9102       return;
9103     }
9104     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9105       ChessSquare piece = WhitePawn;
9106       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9107       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9108       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9109       piece += CharToPiece(ID & 255) - WhitePawn;
9110       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9111       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9112       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9113       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9114       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9115       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9116                                                && gameInfo.variant != VariantGreat
9117                                                && gameInfo.variant != VariantFairy    ) return;
9118       if(piece < EmptySquare) {
9119         pieceDefs = TRUE;
9120         ASSIGN(pieceDesc[piece], buf1);
9121         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9122       }
9123       return;
9124     }
9125     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9126       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9127       Sweep(0);
9128       return;
9129     }
9130     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9131      * want this, I was asked to put it in, and obliged.
9132      */
9133     if (!strncmp(message, "setboard ", 9)) {
9134         Board initial_position;
9135
9136         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9137
9138         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9139             DisplayError(_("Bad FEN received from engine"), 0);
9140             return ;
9141         } else {
9142            Reset(TRUE, FALSE);
9143            CopyBoard(boards[0], initial_position);
9144            initialRulePlies = FENrulePlies;
9145            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9146            else gameMode = MachinePlaysBlack;
9147            DrawPosition(FALSE, boards[currentMove]);
9148         }
9149         return;
9150     }
9151
9152     /*
9153      * Look for communication commands
9154      */
9155     if (!strncmp(message, "telluser ", 9)) {
9156         if(message[9] == '\\' && message[10] == '\\')
9157             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9158         PlayTellSound();
9159         DisplayNote(message + 9);
9160         return;
9161     }
9162     if (!strncmp(message, "tellusererror ", 14)) {
9163         cps->userError = 1;
9164         if(message[14] == '\\' && message[15] == '\\')
9165             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9166         PlayTellSound();
9167         DisplayError(message + 14, 0);
9168         return;
9169     }
9170     if (!strncmp(message, "tellopponent ", 13)) {
9171       if (appData.icsActive) {
9172         if (loggedOn) {
9173           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9174           SendToICS(buf1);
9175         }
9176       } else {
9177         DisplayNote(message + 13);
9178       }
9179       return;
9180     }
9181     if (!strncmp(message, "tellothers ", 11)) {
9182       if (appData.icsActive) {
9183         if (loggedOn) {
9184           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9185           SendToICS(buf1);
9186         }
9187       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9188       return;
9189     }
9190     if (!strncmp(message, "tellall ", 8)) {
9191       if (appData.icsActive) {
9192         if (loggedOn) {
9193           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9194           SendToICS(buf1);
9195         }
9196       } else {
9197         DisplayNote(message + 8);
9198       }
9199       return;
9200     }
9201     if (strncmp(message, "warning", 7) == 0) {
9202         /* Undocumented feature, use tellusererror in new code */
9203         DisplayError(message, 0);
9204         return;
9205     }
9206     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9207         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9208         strcat(realname, " query");
9209         AskQuestion(realname, buf2, buf1, cps->pr);
9210         return;
9211     }
9212     /* Commands from the engine directly to ICS.  We don't allow these to be
9213      *  sent until we are logged on. Crafty kibitzes have been known to
9214      *  interfere with the login process.
9215      */
9216     if (loggedOn) {
9217         if (!strncmp(message, "tellics ", 8)) {
9218             SendToICS(message + 8);
9219             SendToICS("\n");
9220             return;
9221         }
9222         if (!strncmp(message, "tellicsnoalias ", 15)) {
9223             SendToICS(ics_prefix);
9224             SendToICS(message + 15);
9225             SendToICS("\n");
9226             return;
9227         }
9228         /* The following are for backward compatibility only */
9229         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9230             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9231             SendToICS(ics_prefix);
9232             SendToICS(message);
9233             SendToICS("\n");
9234             return;
9235         }
9236     }
9237     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9238         if(initPing == cps->lastPong) {
9239             if(gameInfo.variant == VariantUnknown) {
9240                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9241                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9242                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9243             }
9244             initPing = -1;
9245         }
9246         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9247             abortEngineThink = FALSE;
9248             DisplayMessage("", "");
9249             ThawUI();
9250         }
9251         return;
9252     }
9253     if(!strncmp(message, "highlight ", 10)) {
9254         if(appData.testLegality && !*engineVariant && appData.markers) return;
9255         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9256         return;
9257     }
9258     if(!strncmp(message, "click ", 6)) {
9259         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9260         if(appData.testLegality || !appData.oneClick) return;
9261         sscanf(message+6, "%c%d%c", &f, &y, &c);
9262         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9263         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9264         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9265         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9266         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9267         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9268             LeftClick(Release, lastLeftX, lastLeftY);
9269         controlKey  = (c == ',');
9270         LeftClick(Press, x, y);
9271         LeftClick(Release, x, y);
9272         first.highlight = f;
9273         return;
9274     }
9275     /*
9276      * If the move is illegal, cancel it and redraw the board.
9277      * Also deal with other error cases.  Matching is rather loose
9278      * here to accommodate engines written before the spec.
9279      */
9280     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9281         strncmp(message, "Error", 5) == 0) {
9282         if (StrStr(message, "name") ||
9283             StrStr(message, "rating") || StrStr(message, "?") ||
9284             StrStr(message, "result") || StrStr(message, "board") ||
9285             StrStr(message, "bk") || StrStr(message, "computer") ||
9286             StrStr(message, "variant") || StrStr(message, "hint") ||
9287             StrStr(message, "random") || StrStr(message, "depth") ||
9288             StrStr(message, "accepted")) {
9289             return;
9290         }
9291         if (StrStr(message, "protover")) {
9292           /* Program is responding to input, so it's apparently done
9293              initializing, and this error message indicates it is
9294              protocol version 1.  So we don't need to wait any longer
9295              for it to initialize and send feature commands. */
9296           FeatureDone(cps, 1);
9297           cps->protocolVersion = 1;
9298           return;
9299         }
9300         cps->maybeThinking = FALSE;
9301
9302         if (StrStr(message, "draw")) {
9303             /* Program doesn't have "draw" command */
9304             cps->sendDrawOffers = 0;
9305             return;
9306         }
9307         if (cps->sendTime != 1 &&
9308             (StrStr(message, "time") || StrStr(message, "otim"))) {
9309           /* Program apparently doesn't have "time" or "otim" command */
9310           cps->sendTime = 0;
9311           return;
9312         }
9313         if (StrStr(message, "analyze")) {
9314             cps->analysisSupport = FALSE;
9315             cps->analyzing = FALSE;
9316 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9317             EditGameEvent(); // [HGM] try to preserve loaded game
9318             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9319             DisplayError(buf2, 0);
9320             return;
9321         }
9322         if (StrStr(message, "(no matching move)st")) {
9323           /* Special kludge for GNU Chess 4 only */
9324           cps->stKludge = TRUE;
9325           SendTimeControl(cps, movesPerSession, timeControl,
9326                           timeIncrement, appData.searchDepth,
9327                           searchTime);
9328           return;
9329         }
9330         if (StrStr(message, "(no matching move)sd")) {
9331           /* Special kludge for GNU Chess 4 only */
9332           cps->sdKludge = TRUE;
9333           SendTimeControl(cps, movesPerSession, timeControl,
9334                           timeIncrement, appData.searchDepth,
9335                           searchTime);
9336           return;
9337         }
9338         if (!StrStr(message, "llegal")) {
9339             return;
9340         }
9341         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9342             gameMode == IcsIdle) return;
9343         if (forwardMostMove <= backwardMostMove) return;
9344         if (pausing) PauseEvent();
9345       if(appData.forceIllegal) {
9346             // [HGM] illegal: machine refused move; force position after move into it
9347           SendToProgram("force\n", cps);
9348           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9349                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9350                 // when black is to move, while there might be nothing on a2 or black
9351                 // might already have the move. So send the board as if white has the move.
9352                 // But first we must change the stm of the engine, as it refused the last move
9353                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9354                 if(WhiteOnMove(forwardMostMove)) {
9355                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9356                     SendBoard(cps, forwardMostMove); // kludgeless board
9357                 } else {
9358                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9359                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9360                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9361                 }
9362           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9363             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9364                  gameMode == TwoMachinesPlay)
9365               SendToProgram("go\n", cps);
9366             return;
9367       } else
9368         if (gameMode == PlayFromGameFile) {
9369             /* Stop reading this game file */
9370             gameMode = EditGame;
9371             ModeHighlight();
9372         }
9373         /* [HGM] illegal-move claim should forfeit game when Xboard */
9374         /* only passes fully legal moves                            */
9375         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9376             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9377                                 "False illegal-move claim", GE_XBOARD );
9378             return; // do not take back move we tested as valid
9379         }
9380         currentMove = forwardMostMove-1;
9381         DisplayMove(currentMove-1); /* before DisplayMoveError */
9382         SwitchClocks(forwardMostMove-1); // [HGM] race
9383         DisplayBothClocks();
9384         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9385                 parseList[currentMove], _(cps->which));
9386         DisplayMoveError(buf1);
9387         DrawPosition(FALSE, boards[currentMove]);
9388
9389         SetUserThinkingEnables();
9390         return;
9391     }
9392     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9393         /* Program has a broken "time" command that
9394            outputs a string not ending in newline.
9395            Don't use it. */
9396         cps->sendTime = 0;
9397     }
9398     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9399         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9400             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9401     }
9402
9403     /*
9404      * If chess program startup fails, exit with an error message.
9405      * Attempts to recover here are futile. [HGM] Well, we try anyway
9406      */
9407     if ((StrStr(message, "unknown host") != NULL)
9408         || (StrStr(message, "No remote directory") != NULL)
9409         || (StrStr(message, "not found") != NULL)
9410         || (StrStr(message, "No such file") != NULL)
9411         || (StrStr(message, "can't alloc") != NULL)
9412         || (StrStr(message, "Permission denied") != NULL)) {
9413
9414         cps->maybeThinking = FALSE;
9415         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9416                 _(cps->which), cps->program, cps->host, message);
9417         RemoveInputSource(cps->isr);
9418         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9419             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9420             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9421         }
9422         return;
9423     }
9424
9425     /*
9426      * Look for hint output
9427      */
9428     if (sscanf(message, "Hint: %s", buf1) == 1) {
9429         if (cps == &first && hintRequested) {
9430             hintRequested = FALSE;
9431             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9432                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9433                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9434                                     PosFlags(forwardMostMove),
9435                                     fromY, fromX, toY, toX, promoChar, buf1);
9436                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9437                 DisplayInformation(buf2);
9438             } else {
9439                 /* Hint move could not be parsed!? */
9440               snprintf(buf2, sizeof(buf2),
9441                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9442                         buf1, _(cps->which));
9443                 DisplayError(buf2, 0);
9444             }
9445         } else {
9446           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9447         }
9448         return;
9449     }
9450
9451     /*
9452      * Ignore other messages if game is not in progress
9453      */
9454     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9455         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9456
9457     /*
9458      * look for win, lose, draw, or draw offer
9459      */
9460     if (strncmp(message, "1-0", 3) == 0) {
9461         char *p, *q, *r = "";
9462         p = strchr(message, '{');
9463         if (p) {
9464             q = strchr(p, '}');
9465             if (q) {
9466                 *q = NULLCHAR;
9467                 r = p + 1;
9468             }
9469         }
9470         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9471         return;
9472     } else if (strncmp(message, "0-1", 3) == 0) {
9473         char *p, *q, *r = "";
9474         p = strchr(message, '{');
9475         if (p) {
9476             q = strchr(p, '}');
9477             if (q) {
9478                 *q = NULLCHAR;
9479                 r = p + 1;
9480             }
9481         }
9482         /* Kludge for Arasan 4.1 bug */
9483         if (strcmp(r, "Black resigns") == 0) {
9484             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9485             return;
9486         }
9487         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9488         return;
9489     } else if (strncmp(message, "1/2", 3) == 0) {
9490         char *p, *q, *r = "";
9491         p = strchr(message, '{');
9492         if (p) {
9493             q = strchr(p, '}');
9494             if (q) {
9495                 *q = NULLCHAR;
9496                 r = p + 1;
9497             }
9498         }
9499
9500         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9501         return;
9502
9503     } else if (strncmp(message, "White resign", 12) == 0) {
9504         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9505         return;
9506     } else if (strncmp(message, "Black resign", 12) == 0) {
9507         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9508         return;
9509     } else if (strncmp(message, "White matches", 13) == 0 ||
9510                strncmp(message, "Black matches", 13) == 0   ) {
9511         /* [HGM] ignore GNUShogi noises */
9512         return;
9513     } else if (strncmp(message, "White", 5) == 0 &&
9514                message[5] != '(' &&
9515                StrStr(message, "Black") == NULL) {
9516         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9517         return;
9518     } else if (strncmp(message, "Black", 5) == 0 &&
9519                message[5] != '(') {
9520         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9521         return;
9522     } else if (strcmp(message, "resign") == 0 ||
9523                strcmp(message, "computer resigns") == 0) {
9524         switch (gameMode) {
9525           case MachinePlaysBlack:
9526           case IcsPlayingBlack:
9527             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9528             break;
9529           case MachinePlaysWhite:
9530           case IcsPlayingWhite:
9531             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9532             break;
9533           case TwoMachinesPlay:
9534             if (cps->twoMachinesColor[0] == 'w')
9535               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9536             else
9537               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9538             break;
9539           default:
9540             /* can't happen */
9541             break;
9542         }
9543         return;
9544     } else if (strncmp(message, "opponent mates", 14) == 0) {
9545         switch (gameMode) {
9546           case MachinePlaysBlack:
9547           case IcsPlayingBlack:
9548             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9549             break;
9550           case MachinePlaysWhite:
9551           case IcsPlayingWhite:
9552             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9553             break;
9554           case TwoMachinesPlay:
9555             if (cps->twoMachinesColor[0] == 'w')
9556               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9557             else
9558               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9559             break;
9560           default:
9561             /* can't happen */
9562             break;
9563         }
9564         return;
9565     } else if (strncmp(message, "computer mates", 14) == 0) {
9566         switch (gameMode) {
9567           case MachinePlaysBlack:
9568           case IcsPlayingBlack:
9569             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9570             break;
9571           case MachinePlaysWhite:
9572           case IcsPlayingWhite:
9573             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9574             break;
9575           case TwoMachinesPlay:
9576             if (cps->twoMachinesColor[0] == 'w')
9577               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9578             else
9579               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9580             break;
9581           default:
9582             /* can't happen */
9583             break;
9584         }
9585         return;
9586     } else if (strncmp(message, "checkmate", 9) == 0) {
9587         if (WhiteOnMove(forwardMostMove)) {
9588             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9589         } else {
9590             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9591         }
9592         return;
9593     } else if (strstr(message, "Draw") != NULL ||
9594                strstr(message, "game is a draw") != NULL) {
9595         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9596         return;
9597     } else if (strstr(message, "offer") != NULL &&
9598                strstr(message, "draw") != NULL) {
9599 #if ZIPPY
9600         if (appData.zippyPlay && first.initDone) {
9601             /* Relay offer to ICS */
9602             SendToICS(ics_prefix);
9603             SendToICS("draw\n");
9604         }
9605 #endif
9606         cps->offeredDraw = 2; /* valid until this engine moves twice */
9607         if (gameMode == TwoMachinesPlay) {
9608             if (cps->other->offeredDraw) {
9609                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9610             /* [HGM] in two-machine mode we delay relaying draw offer      */
9611             /* until after we also have move, to see if it is really claim */
9612             }
9613         } else if (gameMode == MachinePlaysWhite ||
9614                    gameMode == MachinePlaysBlack) {
9615           if (userOfferedDraw) {
9616             DisplayInformation(_("Machine accepts your draw offer"));
9617             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9618           } else {
9619             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9620           }
9621         }
9622     }
9623
9624
9625     /*
9626      * Look for thinking output
9627      */
9628     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9629           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9630                                 ) {
9631         int plylev, mvleft, mvtot, curscore, time;
9632         char mvname[MOVE_LEN];
9633         u64 nodes; // [DM]
9634         char plyext;
9635         int ignore = FALSE;
9636         int prefixHint = FALSE;
9637         mvname[0] = NULLCHAR;
9638
9639         switch (gameMode) {
9640           case MachinePlaysBlack:
9641           case IcsPlayingBlack:
9642             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9643             break;
9644           case MachinePlaysWhite:
9645           case IcsPlayingWhite:
9646             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9647             break;
9648           case AnalyzeMode:
9649           case AnalyzeFile:
9650             break;
9651           case IcsObserving: /* [DM] icsEngineAnalyze */
9652             if (!appData.icsEngineAnalyze) ignore = TRUE;
9653             break;
9654           case TwoMachinesPlay:
9655             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9656                 ignore = TRUE;
9657             }
9658             break;
9659           default:
9660             ignore = TRUE;
9661             break;
9662         }
9663
9664         if (!ignore) {
9665             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9666             buf1[0] = NULLCHAR;
9667             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9668                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9669                 char score_buf[MSG_SIZ];
9670
9671                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9672                     nodes += u64Const(0x100000000);
9673
9674                 if (plyext != ' ' && plyext != '\t') {
9675                     time *= 100;
9676                 }
9677
9678                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9679                 if( cps->scoreIsAbsolute &&
9680                     ( gameMode == MachinePlaysBlack ||
9681                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9682                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9683                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9684                      !WhiteOnMove(currentMove)
9685                     ) )
9686                 {
9687                     curscore = -curscore;
9688                 }
9689
9690                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9691
9692                 if(*bestMove) { // rememer time best EPD move was first found
9693                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9694                     ChessMove mt;
9695                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9696                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9697                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9698                 }
9699
9700                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9701                         char buf[MSG_SIZ];
9702                         FILE *f;
9703                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9704                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9705                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9706                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9707                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9708                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9709                                 fclose(f);
9710                         }
9711                         else
9712                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9713                           DisplayError(_("failed writing PV"), 0);
9714                 }
9715
9716                 tempStats.depth = plylev;
9717                 tempStats.nodes = nodes;
9718                 tempStats.time = time;
9719                 tempStats.score = curscore;
9720                 tempStats.got_only_move = 0;
9721
9722                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9723                         int ticklen;
9724
9725                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9726                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9727                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9728                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9729                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9730                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9731                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9732                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9733                 }
9734
9735                 /* Buffer overflow protection */
9736                 if (pv[0] != NULLCHAR) {
9737                     if (strlen(pv) >= sizeof(tempStats.movelist)
9738                         && appData.debugMode) {
9739                         fprintf(debugFP,
9740                                 "PV is too long; using the first %u bytes.\n",
9741                                 (unsigned) sizeof(tempStats.movelist) - 1);
9742                     }
9743
9744                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9745                 } else {
9746                     sprintf(tempStats.movelist, " no PV\n");
9747                 }
9748
9749                 if (tempStats.seen_stat) {
9750                     tempStats.ok_to_send = 1;
9751                 }
9752
9753                 if (strchr(tempStats.movelist, '(') != NULL) {
9754                     tempStats.line_is_book = 1;
9755                     tempStats.nr_moves = 0;
9756                     tempStats.moves_left = 0;
9757                 } else {
9758                     tempStats.line_is_book = 0;
9759                 }
9760
9761                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9762                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9763
9764                 SendProgramStatsToFrontend( cps, &tempStats );
9765
9766                 /*
9767                     [AS] Protect the thinkOutput buffer from overflow... this
9768                     is only useful if buf1 hasn't overflowed first!
9769                 */
9770                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9771                 if(curscore >= MATE_SCORE) 
9772                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9773                 else if(curscore <= -MATE_SCORE) 
9774                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9775                 else
9776                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9777                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9778                          plylev,
9779                          (gameMode == TwoMachinesPlay ?
9780                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9781                          score_buf,
9782                          prefixHint ? lastHint : "",
9783                          prefixHint ? " " : "" );
9784
9785                 if( buf1[0] != NULLCHAR ) {
9786                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9787
9788                     if( strlen(pv) > max_len ) {
9789                         if( appData.debugMode) {
9790                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9791                         }
9792                         pv[max_len+1] = '\0';
9793                     }
9794
9795                     strcat( thinkOutput, pv);
9796                 }
9797
9798                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9799                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9800                     DisplayMove(currentMove - 1);
9801                 }
9802                 return;
9803
9804             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9805                 /* crafty (9.25+) says "(only move) <move>"
9806                  * if there is only 1 legal move
9807                  */
9808                 sscanf(p, "(only move) %s", buf1);
9809                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9810                 sprintf(programStats.movelist, "%s (only move)", buf1);
9811                 programStats.depth = 1;
9812                 programStats.nr_moves = 1;
9813                 programStats.moves_left = 1;
9814                 programStats.nodes = 1;
9815                 programStats.time = 1;
9816                 programStats.got_only_move = 1;
9817
9818                 /* Not really, but we also use this member to
9819                    mean "line isn't going to change" (Crafty
9820                    isn't searching, so stats won't change) */
9821                 programStats.line_is_book = 1;
9822
9823                 SendProgramStatsToFrontend( cps, &programStats );
9824
9825                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9826                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9827                     DisplayMove(currentMove - 1);
9828                 }
9829                 return;
9830             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9831                               &time, &nodes, &plylev, &mvleft,
9832                               &mvtot, mvname) >= 5) {
9833                 /* The stat01: line is from Crafty (9.29+) in response
9834                    to the "." command */
9835                 programStats.seen_stat = 1;
9836                 cps->maybeThinking = TRUE;
9837
9838                 if (programStats.got_only_move || !appData.periodicUpdates)
9839                   return;
9840
9841                 programStats.depth = plylev;
9842                 programStats.time = time;
9843                 programStats.nodes = nodes;
9844                 programStats.moves_left = mvleft;
9845                 programStats.nr_moves = mvtot;
9846                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9847                 programStats.ok_to_send = 1;
9848                 programStats.movelist[0] = '\0';
9849
9850                 SendProgramStatsToFrontend( cps, &programStats );
9851
9852                 return;
9853
9854             } else if (strncmp(message,"++",2) == 0) {
9855                 /* Crafty 9.29+ outputs this */
9856                 programStats.got_fail = 2;
9857                 return;
9858
9859             } else if (strncmp(message,"--",2) == 0) {
9860                 /* Crafty 9.29+ outputs this */
9861                 programStats.got_fail = 1;
9862                 return;
9863
9864             } else if (thinkOutput[0] != NULLCHAR &&
9865                        strncmp(message, "    ", 4) == 0) {
9866                 unsigned message_len;
9867
9868                 p = message;
9869                 while (*p && *p == ' ') p++;
9870
9871                 message_len = strlen( p );
9872
9873                 /* [AS] Avoid buffer overflow */
9874                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9875                     strcat(thinkOutput, " ");
9876                     strcat(thinkOutput, p);
9877                 }
9878
9879                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9880                     strcat(programStats.movelist, " ");
9881                     strcat(programStats.movelist, p);
9882                 }
9883
9884                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9885                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9886                     DisplayMove(currentMove - 1);
9887                 }
9888                 return;
9889             }
9890         }
9891         else {
9892             buf1[0] = NULLCHAR;
9893
9894             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9895                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9896             {
9897                 ChessProgramStats cpstats;
9898
9899                 if (plyext != ' ' && plyext != '\t') {
9900                     time *= 100;
9901                 }
9902
9903                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9904                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9905                     curscore = -curscore;
9906                 }
9907
9908                 cpstats.depth = plylev;
9909                 cpstats.nodes = nodes;
9910                 cpstats.time = time;
9911                 cpstats.score = curscore;
9912                 cpstats.got_only_move = 0;
9913                 cpstats.movelist[0] = '\0';
9914
9915                 if (buf1[0] != NULLCHAR) {
9916                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9917                 }
9918
9919                 cpstats.ok_to_send = 0;
9920                 cpstats.line_is_book = 0;
9921                 cpstats.nr_moves = 0;
9922                 cpstats.moves_left = 0;
9923
9924                 SendProgramStatsToFrontend( cps, &cpstats );
9925             }
9926         }
9927     }
9928 }
9929
9930
9931 /* Parse a game score from the character string "game", and
9932    record it as the history of the current game.  The game
9933    score is NOT assumed to start from the standard position.
9934    The display is not updated in any way.
9935    */
9936 void
9937 ParseGameHistory (char *game)
9938 {
9939     ChessMove moveType;
9940     int fromX, fromY, toX, toY, boardIndex;
9941     char promoChar;
9942     char *p, *q;
9943     char buf[MSG_SIZ];
9944
9945     if (appData.debugMode)
9946       fprintf(debugFP, "Parsing game history: %s\n", game);
9947
9948     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9949     gameInfo.site = StrSave(appData.icsHost);
9950     gameInfo.date = PGNDate();
9951     gameInfo.round = StrSave("-");
9952
9953     /* Parse out names of players */
9954     while (*game == ' ') game++;
9955     p = buf;
9956     while (*game != ' ') *p++ = *game++;
9957     *p = NULLCHAR;
9958     gameInfo.white = StrSave(buf);
9959     while (*game == ' ') game++;
9960     p = buf;
9961     while (*game != ' ' && *game != '\n') *p++ = *game++;
9962     *p = NULLCHAR;
9963     gameInfo.black = StrSave(buf);
9964
9965     /* Parse moves */
9966     boardIndex = blackPlaysFirst ? 1 : 0;
9967     yynewstr(game);
9968     for (;;) {
9969         yyboardindex = boardIndex;
9970         moveType = (ChessMove) Myylex();
9971         switch (moveType) {
9972           case IllegalMove:             /* maybe suicide chess, etc. */
9973   if (appData.debugMode) {
9974     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9975     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9976     setbuf(debugFP, NULL);
9977   }
9978           case WhitePromotion:
9979           case BlackPromotion:
9980           case WhiteNonPromotion:
9981           case BlackNonPromotion:
9982           case NormalMove:
9983           case FirstLeg:
9984           case WhiteCapturesEnPassant:
9985           case BlackCapturesEnPassant:
9986           case WhiteKingSideCastle:
9987           case WhiteQueenSideCastle:
9988           case BlackKingSideCastle:
9989           case BlackQueenSideCastle:
9990           case WhiteKingSideCastleWild:
9991           case WhiteQueenSideCastleWild:
9992           case BlackKingSideCastleWild:
9993           case BlackQueenSideCastleWild:
9994           /* PUSH Fabien */
9995           case WhiteHSideCastleFR:
9996           case WhiteASideCastleFR:
9997           case BlackHSideCastleFR:
9998           case BlackASideCastleFR:
9999           /* POP Fabien */
10000             fromX = currentMoveString[0] - AAA;
10001             fromY = currentMoveString[1] - ONE;
10002             toX = currentMoveString[2] - AAA;
10003             toY = currentMoveString[3] - ONE;
10004             promoChar = currentMoveString[4];
10005             break;
10006           case WhiteDrop:
10007           case BlackDrop:
10008             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10009             fromX = moveType == WhiteDrop ?
10010               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10011             (int) CharToPiece(ToLower(currentMoveString[0]));
10012             fromY = DROP_RANK;
10013             toX = currentMoveString[2] - AAA;
10014             toY = currentMoveString[3] - ONE;
10015             promoChar = NULLCHAR;
10016             break;
10017           case AmbiguousMove:
10018             /* bug? */
10019             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10020   if (appData.debugMode) {
10021     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10022     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10023     setbuf(debugFP, NULL);
10024   }
10025             DisplayError(buf, 0);
10026             return;
10027           case ImpossibleMove:
10028             /* bug? */
10029             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10030   if (appData.debugMode) {
10031     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10032     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10033     setbuf(debugFP, NULL);
10034   }
10035             DisplayError(buf, 0);
10036             return;
10037           case EndOfFile:
10038             if (boardIndex < backwardMostMove) {
10039                 /* Oops, gap.  How did that happen? */
10040                 DisplayError(_("Gap in move list"), 0);
10041                 return;
10042             }
10043             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10044             if (boardIndex > forwardMostMove) {
10045                 forwardMostMove = boardIndex;
10046             }
10047             return;
10048           case ElapsedTime:
10049             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10050                 strcat(parseList[boardIndex-1], " ");
10051                 strcat(parseList[boardIndex-1], yy_text);
10052             }
10053             continue;
10054           case Comment:
10055           case PGNTag:
10056           case NAG:
10057           default:
10058             /* ignore */
10059             continue;
10060           case WhiteWins:
10061           case BlackWins:
10062           case GameIsDrawn:
10063           case GameUnfinished:
10064             if (gameMode == IcsExamining) {
10065                 if (boardIndex < backwardMostMove) {
10066                     /* Oops, gap.  How did that happen? */
10067                     return;
10068                 }
10069                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10070                 return;
10071             }
10072             gameInfo.result = moveType;
10073             p = strchr(yy_text, '{');
10074             if (p == NULL) p = strchr(yy_text, '(');
10075             if (p == NULL) {
10076                 p = yy_text;
10077                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10078             } else {
10079                 q = strchr(p, *p == '{' ? '}' : ')');
10080                 if (q != NULL) *q = NULLCHAR;
10081                 p++;
10082             }
10083             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10084             gameInfo.resultDetails = StrSave(p);
10085             continue;
10086         }
10087         if (boardIndex >= forwardMostMove &&
10088             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10089             backwardMostMove = blackPlaysFirst ? 1 : 0;
10090             return;
10091         }
10092         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10093                                  fromY, fromX, toY, toX, promoChar,
10094                                  parseList[boardIndex]);
10095         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10096         /* currentMoveString is set as a side-effect of yylex */
10097         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10098         strcat(moveList[boardIndex], "\n");
10099         boardIndex++;
10100         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10101         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10102           case MT_NONE:
10103           case MT_STALEMATE:
10104           default:
10105             break;
10106           case MT_CHECK:
10107             if(!IS_SHOGI(gameInfo.variant))
10108                 strcat(parseList[boardIndex - 1], "+");
10109             break;
10110           case MT_CHECKMATE:
10111           case MT_STAINMATE:
10112             strcat(parseList[boardIndex - 1], "#");
10113             break;
10114         }
10115     }
10116 }
10117
10118
10119 /* Apply a move to the given board  */
10120 void
10121 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10122 {
10123   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10124   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10125
10126     /* [HGM] compute & store e.p. status and castling rights for new position */
10127     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10128
10129       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10130       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10131       board[EP_STATUS] = EP_NONE;
10132       board[EP_FILE] = board[EP_RANK] = 100;
10133
10134   if (fromY == DROP_RANK) {
10135         /* must be first */
10136         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10137             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10138             return;
10139         }
10140         piece = board[toY][toX] = (ChessSquare) fromX;
10141   } else {
10142 //      ChessSquare victim;
10143       int i;
10144
10145       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10146 //           victim = board[killY][killX],
10147            killed = board[killY][killX],
10148            board[killY][killX] = EmptySquare,
10149            board[EP_STATUS] = EP_CAPTURE;
10150            if( kill2X >= 0 && kill2Y >= 0)
10151              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10152       }
10153
10154       if( board[toY][toX] != EmptySquare ) {
10155            board[EP_STATUS] = EP_CAPTURE;
10156            if( (fromX != toX || fromY != toY) && // not igui!
10157                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10158                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10159                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10160            }
10161       }
10162
10163       pawn = board[fromY][fromX];
10164       if( pawn == WhiteLance || pawn == BlackLance ) {
10165            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10166                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10167                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10168            }
10169       }
10170       if( pawn == WhitePawn ) {
10171            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10172                board[EP_STATUS] = EP_PAWN_MOVE;
10173            if( toY-fromY>=2) {
10174                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10175                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10176                         gameInfo.variant != VariantBerolina || toX < fromX)
10177                       board[EP_STATUS] = toX | berolina;
10178                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10179                         gameInfo.variant != VariantBerolina || toX > fromX)
10180                       board[EP_STATUS] = toX;
10181            }
10182       } else
10183       if( pawn == BlackPawn ) {
10184            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10185                board[EP_STATUS] = EP_PAWN_MOVE;
10186            if( toY-fromY<= -2) {
10187                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10188                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10189                         gameInfo.variant != VariantBerolina || toX < fromX)
10190                       board[EP_STATUS] = toX | berolina;
10191                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10192                         gameInfo.variant != VariantBerolina || toX > fromX)
10193                       board[EP_STATUS] = toX;
10194            }
10195        }
10196
10197        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10198        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10199        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10200        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10201
10202        for(i=0; i<nrCastlingRights; i++) {
10203            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10204               board[CASTLING][i] == toX   && castlingRank[i] == toY
10205              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10206        }
10207
10208        if(gameInfo.variant == VariantSChess) { // update virginity
10209            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10210            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10211            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10212            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10213        }
10214
10215      if (fromX == toX && fromY == toY) return;
10216
10217      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10218      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10219      if(gameInfo.variant == VariantKnightmate)
10220          king += (int) WhiteUnicorn - (int) WhiteKing;
10221
10222     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10223        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10224         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10225         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10226         board[EP_STATUS] = EP_NONE; // capture was fake!
10227     } else
10228     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10229         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10230         board[toY][toX] = piece;
10231         board[EP_STATUS] = EP_NONE; // capture was fake!
10232     } else
10233     /* Code added by Tord: */
10234     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10235     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10236         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10237       board[EP_STATUS] = EP_NONE; // capture was fake!
10238       board[fromY][fromX] = EmptySquare;
10239       board[toY][toX] = EmptySquare;
10240       if((toX > fromX) != (piece == WhiteRook)) {
10241         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10242       } else {
10243         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10244       }
10245     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10246                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10247       board[EP_STATUS] = EP_NONE;
10248       board[fromY][fromX] = EmptySquare;
10249       board[toY][toX] = EmptySquare;
10250       if((toX > fromX) != (piece == BlackRook)) {
10251         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10252       } else {
10253         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10254       }
10255     /* End of code added by Tord */
10256
10257     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10258         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10259         board[toY][toX] = piece;
10260     } else if (board[fromY][fromX] == king
10261         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10262         && toY == fromY && toX > fromX+1) {
10263         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10264         board[fromY][toX-1] = board[fromY][rookX];
10265         board[fromY][rookX] = EmptySquare;
10266         board[fromY][fromX] = EmptySquare;
10267         board[toY][toX] = king;
10268     } else if (board[fromY][fromX] == king
10269         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10270                && toY == fromY && toX < fromX-1) {
10271         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10272         board[fromY][toX+1] = board[fromY][rookX];
10273         board[fromY][rookX] = EmptySquare;
10274         board[fromY][fromX] = EmptySquare;
10275         board[toY][toX] = king;
10276     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10277                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10278                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10279                ) {
10280         /* white pawn promotion */
10281         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10282         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10283             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10284         board[fromY][fromX] = EmptySquare;
10285     } else if ((fromY >= BOARD_HEIGHT>>1)
10286                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10287                && (toX != fromX)
10288                && gameInfo.variant != VariantXiangqi
10289                && gameInfo.variant != VariantBerolina
10290                && (pawn == WhitePawn)
10291                && (board[toY][toX] == EmptySquare)) {
10292         board[fromY][fromX] = EmptySquare;
10293         board[toY][toX] = piece;
10294         if(toY == epRank - 128 + 1)
10295             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10296         else
10297             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10298     } else if ((fromY == BOARD_HEIGHT-4)
10299                && (toX == fromX)
10300                && gameInfo.variant == VariantBerolina
10301                && (board[fromY][fromX] == WhitePawn)
10302                && (board[toY][toX] == EmptySquare)) {
10303         board[fromY][fromX] = EmptySquare;
10304         board[toY][toX] = WhitePawn;
10305         if(oldEP & EP_BEROLIN_A) {
10306                 captured = board[fromY][fromX-1];
10307                 board[fromY][fromX-1] = EmptySquare;
10308         }else{  captured = board[fromY][fromX+1];
10309                 board[fromY][fromX+1] = EmptySquare;
10310         }
10311     } else if (board[fromY][fromX] == king
10312         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10313                && toY == fromY && toX > fromX+1) {
10314         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10315         board[fromY][toX-1] = board[fromY][rookX];
10316         board[fromY][rookX] = EmptySquare;
10317         board[fromY][fromX] = EmptySquare;
10318         board[toY][toX] = king;
10319     } else if (board[fromY][fromX] == king
10320         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10321                && toY == fromY && toX < fromX-1) {
10322         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10323         board[fromY][toX+1] = board[fromY][rookX];
10324         board[fromY][rookX] = EmptySquare;
10325         board[fromY][fromX] = EmptySquare;
10326         board[toY][toX] = king;
10327     } else if (fromY == 7 && fromX == 3
10328                && board[fromY][fromX] == BlackKing
10329                && toY == 7 && toX == 5) {
10330         board[fromY][fromX] = EmptySquare;
10331         board[toY][toX] = BlackKing;
10332         board[fromY][7] = EmptySquare;
10333         board[toY][4] = BlackRook;
10334     } else if (fromY == 7 && fromX == 3
10335                && board[fromY][fromX] == BlackKing
10336                && toY == 7 && toX == 1) {
10337         board[fromY][fromX] = EmptySquare;
10338         board[toY][toX] = BlackKing;
10339         board[fromY][0] = EmptySquare;
10340         board[toY][2] = BlackRook;
10341     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10342                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10343                && toY < promoRank && promoChar
10344                ) {
10345         /* black pawn promotion */
10346         board[toY][toX] = CharToPiece(ToLower(promoChar));
10347         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10348             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10349         board[fromY][fromX] = EmptySquare;
10350     } else if ((fromY < BOARD_HEIGHT>>1)
10351                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10352                && (toX != fromX)
10353                && gameInfo.variant != VariantXiangqi
10354                && gameInfo.variant != VariantBerolina
10355                && (pawn == BlackPawn)
10356                && (board[toY][toX] == EmptySquare)) {
10357         board[fromY][fromX] = EmptySquare;
10358         board[toY][toX] = piece;
10359         if(toY == epRank - 128 - 1)
10360             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10361         else
10362             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10363     } else if ((fromY == 3)
10364                && (toX == fromX)
10365                && gameInfo.variant == VariantBerolina
10366                && (board[fromY][fromX] == BlackPawn)
10367                && (board[toY][toX] == EmptySquare)) {
10368         board[fromY][fromX] = EmptySquare;
10369         board[toY][toX] = BlackPawn;
10370         if(oldEP & EP_BEROLIN_A) {
10371                 captured = board[fromY][fromX-1];
10372                 board[fromY][fromX-1] = EmptySquare;
10373         }else{  captured = board[fromY][fromX+1];
10374                 board[fromY][fromX+1] = EmptySquare;
10375         }
10376     } else {
10377         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10378         board[fromY][fromX] = EmptySquare;
10379         board[toY][toX] = piece;
10380     }
10381   }
10382
10383     if (gameInfo.holdingsWidth != 0) {
10384
10385       /* !!A lot more code needs to be written to support holdings  */
10386       /* [HGM] OK, so I have written it. Holdings are stored in the */
10387       /* penultimate board files, so they are automaticlly stored   */
10388       /* in the game history.                                       */
10389       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10390                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10391         /* Delete from holdings, by decreasing count */
10392         /* and erasing image if necessary            */
10393         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10394         if(p < (int) BlackPawn) { /* white drop */
10395              p -= (int)WhitePawn;
10396                  p = PieceToNumber((ChessSquare)p);
10397              if(p >= gameInfo.holdingsSize) p = 0;
10398              if(--board[p][BOARD_WIDTH-2] <= 0)
10399                   board[p][BOARD_WIDTH-1] = EmptySquare;
10400              if((int)board[p][BOARD_WIDTH-2] < 0)
10401                         board[p][BOARD_WIDTH-2] = 0;
10402         } else {                  /* black drop */
10403              p -= (int)BlackPawn;
10404                  p = PieceToNumber((ChessSquare)p);
10405              if(p >= gameInfo.holdingsSize) p = 0;
10406              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10407                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10408              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10409                         board[BOARD_HEIGHT-1-p][1] = 0;
10410         }
10411       }
10412       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10413           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10414         /* [HGM] holdings: Add to holdings, if holdings exist */
10415         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10416                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10417                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10418         }
10419         p = (int) captured;
10420         if (p >= (int) BlackPawn) {
10421           p -= (int)BlackPawn;
10422           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10423                   /* Restore shogi-promoted piece to its original  first */
10424                   captured = (ChessSquare) (DEMOTED(captured));
10425                   p = DEMOTED(p);
10426           }
10427           p = PieceToNumber((ChessSquare)p);
10428           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10429           board[p][BOARD_WIDTH-2]++;
10430           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10431         } else {
10432           p -= (int)WhitePawn;
10433           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10434                   captured = (ChessSquare) (DEMOTED(captured));
10435                   p = DEMOTED(p);
10436           }
10437           p = PieceToNumber((ChessSquare)p);
10438           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10439           board[BOARD_HEIGHT-1-p][1]++;
10440           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10441         }
10442       }
10443     } else if (gameInfo.variant == VariantAtomic) {
10444       if (captured != EmptySquare) {
10445         int y, x;
10446         for (y = toY-1; y <= toY+1; y++) {
10447           for (x = toX-1; x <= toX+1; x++) {
10448             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10449                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10450               board[y][x] = EmptySquare;
10451             }
10452           }
10453         }
10454         board[toY][toX] = EmptySquare;
10455       }
10456     }
10457
10458     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10459         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10460     } else
10461     if(promoChar == '+') {
10462         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10463         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10464         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10465           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10466     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10467         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10468         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10469            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10470         board[toY][toX] = newPiece;
10471     }
10472     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10473                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10474         // [HGM] superchess: take promotion piece out of holdings
10475         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10476         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10477             if(!--board[k][BOARD_WIDTH-2])
10478                 board[k][BOARD_WIDTH-1] = EmptySquare;
10479         } else {
10480             if(!--board[BOARD_HEIGHT-1-k][1])
10481                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10482         }
10483     }
10484 }
10485
10486 /* Updates forwardMostMove */
10487 void
10488 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10489 {
10490     int x = toX, y = toY;
10491     char *s = parseList[forwardMostMove];
10492     ChessSquare p = boards[forwardMostMove][toY][toX];
10493 //    forwardMostMove++; // [HGM] bare: moved downstream
10494
10495     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10496     (void) CoordsToAlgebraic(boards[forwardMostMove],
10497                              PosFlags(forwardMostMove),
10498                              fromY, fromX, y, x, (killX < 0)*promoChar,
10499                              s);
10500     if(killX >= 0 && killY >= 0)
10501         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
10502
10503     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10504         int timeLeft; static int lastLoadFlag=0; int king, piece;
10505         piece = boards[forwardMostMove][fromY][fromX];
10506         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10507         if(gameInfo.variant == VariantKnightmate)
10508             king += (int) WhiteUnicorn - (int) WhiteKing;
10509         if(forwardMostMove == 0) {
10510             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10511                 fprintf(serverMoves, "%s;", UserName());
10512             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10513                 fprintf(serverMoves, "%s;", second.tidy);
10514             fprintf(serverMoves, "%s;", first.tidy);
10515             if(gameMode == MachinePlaysWhite)
10516                 fprintf(serverMoves, "%s;", UserName());
10517             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10518                 fprintf(serverMoves, "%s;", second.tidy);
10519         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10520         lastLoadFlag = loadFlag;
10521         // print base move
10522         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10523         // print castling suffix
10524         if( toY == fromY && piece == king ) {
10525             if(toX-fromX > 1)
10526                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10527             if(fromX-toX >1)
10528                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10529         }
10530         // e.p. suffix
10531         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10532              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10533              boards[forwardMostMove][toY][toX] == EmptySquare
10534              && fromX != toX && fromY != toY)
10535                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10536         // promotion suffix
10537         if(promoChar != NULLCHAR) {
10538             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10539                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10540                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10541             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10542         }
10543         if(!loadFlag) {
10544                 char buf[MOVE_LEN*2], *p; int len;
10545             fprintf(serverMoves, "/%d/%d",
10546                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10547             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10548             else                      timeLeft = blackTimeRemaining/1000;
10549             fprintf(serverMoves, "/%d", timeLeft);
10550                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10551                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10552                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10553                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10554             fprintf(serverMoves, "/%s", buf);
10555         }
10556         fflush(serverMoves);
10557     }
10558
10559     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10560         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10561       return;
10562     }
10563     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10564     if (commentList[forwardMostMove+1] != NULL) {
10565         free(commentList[forwardMostMove+1]);
10566         commentList[forwardMostMove+1] = NULL;
10567     }
10568     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10569     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10570     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10571     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10572     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10573     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10574     adjustedClock = FALSE;
10575     gameInfo.result = GameUnfinished;
10576     if (gameInfo.resultDetails != NULL) {
10577         free(gameInfo.resultDetails);
10578         gameInfo.resultDetails = NULL;
10579     }
10580     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10581                               moveList[forwardMostMove - 1]);
10582     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10583       case MT_NONE:
10584       case MT_STALEMATE:
10585       default:
10586         break;
10587       case MT_CHECK:
10588         if(!IS_SHOGI(gameInfo.variant))
10589             strcat(parseList[forwardMostMove - 1], "+");
10590         break;
10591       case MT_CHECKMATE:
10592       case MT_STAINMATE:
10593         strcat(parseList[forwardMostMove - 1], "#");
10594         break;
10595     }
10596 }
10597
10598 /* Updates currentMove if not pausing */
10599 void
10600 ShowMove (int fromX, int fromY, int toX, int toY)
10601 {
10602     int instant = (gameMode == PlayFromGameFile) ?
10603         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10604     if(appData.noGUI) return;
10605     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10606         if (!instant) {
10607             if (forwardMostMove == currentMove + 1) {
10608                 AnimateMove(boards[forwardMostMove - 1],
10609                             fromX, fromY, toX, toY);
10610             }
10611         }
10612         currentMove = forwardMostMove;
10613     }
10614
10615     killX = killY = -1; // [HGM] lion: used up
10616
10617     if (instant) return;
10618
10619     DisplayMove(currentMove - 1);
10620     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10621             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10622                 SetHighlights(fromX, fromY, toX, toY);
10623             }
10624     }
10625     DrawPosition(FALSE, boards[currentMove]);
10626     DisplayBothClocks();
10627     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10628 }
10629
10630 void
10631 SendEgtPath (ChessProgramState *cps)
10632 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10633         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10634
10635         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10636
10637         while(*p) {
10638             char c, *q = name+1, *r, *s;
10639
10640             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10641             while(*p && *p != ',') *q++ = *p++;
10642             *q++ = ':'; *q = 0;
10643             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10644                 strcmp(name, ",nalimov:") == 0 ) {
10645                 // take nalimov path from the menu-changeable option first, if it is defined
10646               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10647                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10648             } else
10649             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10650                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10651                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10652                 s = r = StrStr(s, ":") + 1; // beginning of path info
10653                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10654                 c = *r; *r = 0;             // temporarily null-terminate path info
10655                     *--q = 0;               // strip of trailig ':' from name
10656                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10657                 *r = c;
10658                 SendToProgram(buf,cps);     // send egtbpath command for this format
10659             }
10660             if(*p == ',') p++; // read away comma to position for next format name
10661         }
10662 }
10663
10664 static int
10665 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10666 {
10667       int width = 8, height = 8, holdings = 0;             // most common sizes
10668       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10669       // correct the deviations default for each variant
10670       if( v == VariantXiangqi ) width = 9,  height = 10;
10671       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10672       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10673       if( v == VariantCapablanca || v == VariantCapaRandom ||
10674           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10675                                 width = 10;
10676       if( v == VariantCourier ) width = 12;
10677       if( v == VariantSuper )                            holdings = 8;
10678       if( v == VariantGreat )   width = 10,              holdings = 8;
10679       if( v == VariantSChess )                           holdings = 7;
10680       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10681       if( v == VariantChuChess) width = 10, height = 10;
10682       if( v == VariantChu )     width = 12, height = 12;
10683       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10684              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10685              holdingsSize >= 0 && holdingsSize != holdings;
10686 }
10687
10688 char variantError[MSG_SIZ];
10689
10690 char *
10691 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10692 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10693       char *p, *variant = VariantName(v);
10694       static char b[MSG_SIZ];
10695       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10696            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10697                                                holdingsSize, variant); // cook up sized variant name
10698            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10699            if(StrStr(list, b) == NULL) {
10700                // specific sized variant not known, check if general sizing allowed
10701                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10702                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10703                             boardWidth, boardHeight, holdingsSize, engine);
10704                    return NULL;
10705                }
10706                /* [HGM] here we really should compare with the maximum supported board size */
10707            }
10708       } else snprintf(b, MSG_SIZ,"%s", variant);
10709       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10710       p = StrStr(list, b);
10711       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10712       if(p == NULL) {
10713           // occurs not at all in list, or only as sub-string
10714           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10715           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10716               int l = strlen(variantError);
10717               char *q;
10718               while(p != list && p[-1] != ',') p--;
10719               q = strchr(p, ',');
10720               if(q) *q = NULLCHAR;
10721               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10722               if(q) *q= ',';
10723           }
10724           return NULL;
10725       }
10726       return b;
10727 }
10728
10729 void
10730 InitChessProgram (ChessProgramState *cps, int setup)
10731 /* setup needed to setup FRC opening position */
10732 {
10733     char buf[MSG_SIZ], *b;
10734     if (appData.noChessProgram) return;
10735     hintRequested = FALSE;
10736     bookRequested = FALSE;
10737
10738     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10739     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10740     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10741     if(cps->memSize) { /* [HGM] memory */
10742       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10743         SendToProgram(buf, cps);
10744     }
10745     SendEgtPath(cps); /* [HGM] EGT */
10746     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10747       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10748         SendToProgram(buf, cps);
10749     }
10750
10751     setboardSpoiledMachineBlack = FALSE;
10752     SendToProgram(cps->initString, cps);
10753     if (gameInfo.variant != VariantNormal &&
10754         gameInfo.variant != VariantLoadable
10755         /* [HGM] also send variant if board size non-standard */
10756         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10757
10758       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10759                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10760       if (b == NULL) {
10761         VariantClass v;
10762         char c, *q = cps->variants, *p = strchr(q, ',');
10763         if(p) *p = NULLCHAR;
10764         v = StringToVariant(q);
10765         DisplayError(variantError, 0);
10766         if(v != VariantUnknown && cps == &first) {
10767             int w, h, s;
10768             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10769                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10770             ASSIGN(appData.variant, q);
10771             Reset(TRUE, FALSE);
10772         }
10773         if(p) *p = ',';
10774         return;
10775       }
10776
10777       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10778       SendToProgram(buf, cps);
10779     }
10780     currentlyInitializedVariant = gameInfo.variant;
10781
10782     /* [HGM] send opening position in FRC to first engine */
10783     if(setup) {
10784           SendToProgram("force\n", cps);
10785           SendBoard(cps, 0);
10786           /* engine is now in force mode! Set flag to wake it up after first move. */
10787           setboardSpoiledMachineBlack = 1;
10788     }
10789
10790     if (cps->sendICS) {
10791       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10792       SendToProgram(buf, cps);
10793     }
10794     cps->maybeThinking = FALSE;
10795     cps->offeredDraw = 0;
10796     if (!appData.icsActive) {
10797         SendTimeControl(cps, movesPerSession, timeControl,
10798                         timeIncrement, appData.searchDepth,
10799                         searchTime);
10800     }
10801     if (appData.showThinking
10802         // [HGM] thinking: four options require thinking output to be sent
10803         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10804                                 ) {
10805         SendToProgram("post\n", cps);
10806     }
10807     SendToProgram("hard\n", cps);
10808     if (!appData.ponderNextMove) {
10809         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10810            it without being sure what state we are in first.  "hard"
10811            is not a toggle, so that one is OK.
10812          */
10813         SendToProgram("easy\n", cps);
10814     }
10815     if (cps->usePing) {
10816       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10817       SendToProgram(buf, cps);
10818     }
10819     cps->initDone = TRUE;
10820     ClearEngineOutputPane(cps == &second);
10821 }
10822
10823
10824 void
10825 ResendOptions (ChessProgramState *cps)
10826 { // send the stored value of the options
10827   int i;
10828   char buf[MSG_SIZ];
10829   Option *opt = cps->option;
10830   for(i=0; i<cps->nrOptions; i++, opt++) {
10831       switch(opt->type) {
10832         case Spin:
10833         case Slider:
10834         case CheckBox:
10835             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10836           break;
10837         case ComboBox:
10838           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10839           break;
10840         default:
10841             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10842           break;
10843         case Button:
10844         case SaveButton:
10845           continue;
10846       }
10847       SendToProgram(buf, cps);
10848   }
10849 }
10850
10851 void
10852 StartChessProgram (ChessProgramState *cps)
10853 {
10854     char buf[MSG_SIZ];
10855     int err;
10856
10857     if (appData.noChessProgram) return;
10858     cps->initDone = FALSE;
10859
10860     if (strcmp(cps->host, "localhost") == 0) {
10861         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10862     } else if (*appData.remoteShell == NULLCHAR) {
10863         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10864     } else {
10865         if (*appData.remoteUser == NULLCHAR) {
10866           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10867                     cps->program);
10868         } else {
10869           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10870                     cps->host, appData.remoteUser, cps->program);
10871         }
10872         err = StartChildProcess(buf, "", &cps->pr);
10873     }
10874
10875     if (err != 0) {
10876       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10877         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10878         if(cps != &first) return;
10879         appData.noChessProgram = TRUE;
10880         ThawUI();
10881         SetNCPMode();
10882 //      DisplayFatalError(buf, err, 1);
10883 //      cps->pr = NoProc;
10884 //      cps->isr = NULL;
10885         return;
10886     }
10887
10888     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10889     if (cps->protocolVersion > 1) {
10890       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10891       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10892         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10893         cps->comboCnt = 0;  //                and values of combo boxes
10894       }
10895       SendToProgram(buf, cps);
10896       if(cps->reload) ResendOptions(cps);
10897     } else {
10898       SendToProgram("xboard\n", cps);
10899     }
10900 }
10901
10902 void
10903 TwoMachinesEventIfReady P((void))
10904 {
10905   static int curMess = 0;
10906   if (first.lastPing != first.lastPong) {
10907     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10908     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10909     return;
10910   }
10911   if (second.lastPing != second.lastPong) {
10912     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10913     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10914     return;
10915   }
10916   DisplayMessage("", ""); curMess = 0;
10917   TwoMachinesEvent();
10918 }
10919
10920 char *
10921 MakeName (char *template)
10922 {
10923     time_t clock;
10924     struct tm *tm;
10925     static char buf[MSG_SIZ];
10926     char *p = buf;
10927     int i;
10928
10929     clock = time((time_t *)NULL);
10930     tm = localtime(&clock);
10931
10932     while(*p++ = *template++) if(p[-1] == '%') {
10933         switch(*template++) {
10934           case 0:   *p = 0; return buf;
10935           case 'Y': i = tm->tm_year+1900; break;
10936           case 'y': i = tm->tm_year-100; break;
10937           case 'M': i = tm->tm_mon+1; break;
10938           case 'd': i = tm->tm_mday; break;
10939           case 'h': i = tm->tm_hour; break;
10940           case 'm': i = tm->tm_min; break;
10941           case 's': i = tm->tm_sec; break;
10942           default:  i = 0;
10943         }
10944         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10945     }
10946     return buf;
10947 }
10948
10949 int
10950 CountPlayers (char *p)
10951 {
10952     int n = 0;
10953     while(p = strchr(p, '\n')) p++, n++; // count participants
10954     return n;
10955 }
10956
10957 FILE *
10958 WriteTourneyFile (char *results, FILE *f)
10959 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10960     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10961     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10962         // create a file with tournament description
10963         fprintf(f, "-participants {%s}\n", appData.participants);
10964         fprintf(f, "-seedBase %d\n", appData.seedBase);
10965         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10966         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10967         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10968         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10969         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10970         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10971         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10972         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10973         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10974         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10975         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10976         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10977         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10978         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10979         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10980         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10981         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10982         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10983         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10984         fprintf(f, "-smpCores %d\n", appData.smpCores);
10985         if(searchTime > 0)
10986                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10987         else {
10988                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10989                 fprintf(f, "-tc %s\n", appData.timeControl);
10990                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10991         }
10992         fprintf(f, "-results \"%s\"\n", results);
10993     }
10994     return f;
10995 }
10996
10997 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10998
10999 void
11000 Substitute (char *participants, int expunge)
11001 {
11002     int i, changed, changes=0, nPlayers=0;
11003     char *p, *q, *r, buf[MSG_SIZ];
11004     if(participants == NULL) return;
11005     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11006     r = p = participants; q = appData.participants;
11007     while(*p && *p == *q) {
11008         if(*p == '\n') r = p+1, nPlayers++;
11009         p++; q++;
11010     }
11011     if(*p) { // difference
11012         while(*p && *p++ != '\n');
11013         while(*q && *q++ != '\n');
11014       changed = nPlayers;
11015         changes = 1 + (strcmp(p, q) != 0);
11016     }
11017     if(changes == 1) { // a single engine mnemonic was changed
11018         q = r; while(*q) nPlayers += (*q++ == '\n');
11019         p = buf; while(*r && (*p = *r++) != '\n') p++;
11020         *p = NULLCHAR;
11021         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11022         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11023         if(mnemonic[i]) { // The substitute is valid
11024             FILE *f;
11025             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11026                 flock(fileno(f), LOCK_EX);
11027                 ParseArgsFromFile(f);
11028                 fseek(f, 0, SEEK_SET);
11029                 FREE(appData.participants); appData.participants = participants;
11030                 if(expunge) { // erase results of replaced engine
11031                     int len = strlen(appData.results), w, b, dummy;
11032                     for(i=0; i<len; i++) {
11033                         Pairing(i, nPlayers, &w, &b, &dummy);
11034                         if((w == changed || b == changed) && appData.results[i] == '*') {
11035                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11036                             fclose(f);
11037                             return;
11038                         }
11039                     }
11040                     for(i=0; i<len; i++) {
11041                         Pairing(i, nPlayers, &w, &b, &dummy);
11042                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11043                     }
11044                 }
11045                 WriteTourneyFile(appData.results, f);
11046                 fclose(f); // release lock
11047                 return;
11048             }
11049         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11050     }
11051     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11052     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11053     free(participants);
11054     return;
11055 }
11056
11057 int
11058 CheckPlayers (char *participants)
11059 {
11060         int i;
11061         char buf[MSG_SIZ], *p;
11062         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11063         while(p = strchr(participants, '\n')) {
11064             *p = NULLCHAR;
11065             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11066             if(!mnemonic[i]) {
11067                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11068                 *p = '\n';
11069                 DisplayError(buf, 0);
11070                 return 1;
11071             }
11072             *p = '\n';
11073             participants = p + 1;
11074         }
11075         return 0;
11076 }
11077
11078 int
11079 CreateTourney (char *name)
11080 {
11081         FILE *f;
11082         if(matchMode && strcmp(name, appData.tourneyFile)) {
11083              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11084         }
11085         if(name[0] == NULLCHAR) {
11086             if(appData.participants[0])
11087                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11088             return 0;
11089         }
11090         f = fopen(name, "r");
11091         if(f) { // file exists
11092             ASSIGN(appData.tourneyFile, name);
11093             ParseArgsFromFile(f); // parse it
11094         } else {
11095             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11096             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11097                 DisplayError(_("Not enough participants"), 0);
11098                 return 0;
11099             }
11100             if(CheckPlayers(appData.participants)) return 0;
11101             ASSIGN(appData.tourneyFile, name);
11102             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11103             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11104         }
11105         fclose(f);
11106         appData.noChessProgram = FALSE;
11107         appData.clockMode = TRUE;
11108         SetGNUMode();
11109         return 1;
11110 }
11111
11112 int
11113 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11114 {
11115     char buf[MSG_SIZ], *p, *q;
11116     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11117     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11118     skip = !all && group[0]; // if group requested, we start in skip mode
11119     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11120         p = names; q = buf; header = 0;
11121         while(*p && *p != '\n') *q++ = *p++;
11122         *q = 0;
11123         if(*p == '\n') p++;
11124         if(buf[0] == '#') {
11125             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11126             depth++; // we must be entering a new group
11127             if(all) continue; // suppress printing group headers when complete list requested
11128             header = 1;
11129             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11130         }
11131         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11132         if(engineList[i]) free(engineList[i]);
11133         engineList[i] = strdup(buf);
11134         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11135         if(engineMnemonic[i]) free(engineMnemonic[i]);
11136         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11137             strcat(buf, " (");
11138             sscanf(q + 8, "%s", buf + strlen(buf));
11139             strcat(buf, ")");
11140         }
11141         engineMnemonic[i] = strdup(buf);
11142         i++;
11143     }
11144     engineList[i] = engineMnemonic[i] = NULL;
11145     return i;
11146 }
11147
11148 // following implemented as macro to avoid type limitations
11149 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11150
11151 void
11152 SwapEngines (int n)
11153 {   // swap settings for first engine and other engine (so far only some selected options)
11154     int h;
11155     char *p;
11156     if(n == 0) return;
11157     SWAP(directory, p)
11158     SWAP(chessProgram, p)
11159     SWAP(isUCI, h)
11160     SWAP(hasOwnBookUCI, h)
11161     SWAP(protocolVersion, h)
11162     SWAP(reuse, h)
11163     SWAP(scoreIsAbsolute, h)
11164     SWAP(timeOdds, h)
11165     SWAP(logo, p)
11166     SWAP(pgnName, p)
11167     SWAP(pvSAN, h)
11168     SWAP(engOptions, p)
11169     SWAP(engInitString, p)
11170     SWAP(computerString, p)
11171     SWAP(features, p)
11172     SWAP(fenOverride, p)
11173     SWAP(NPS, h)
11174     SWAP(accumulateTC, h)
11175     SWAP(drawDepth, h)
11176     SWAP(host, p)
11177     SWAP(pseudo, h)
11178 }
11179
11180 int
11181 GetEngineLine (char *s, int n)
11182 {
11183     int i;
11184     char buf[MSG_SIZ];
11185     extern char *icsNames;
11186     if(!s || !*s) return 0;
11187     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11188     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11189     if(!mnemonic[i]) return 0;
11190     if(n == 11) return 1; // just testing if there was a match
11191     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11192     if(n == 1) SwapEngines(n);
11193     ParseArgsFromString(buf);
11194     if(n == 1) SwapEngines(n);
11195     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11196         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11197         ParseArgsFromString(buf);
11198     }
11199     return 1;
11200 }
11201
11202 int
11203 SetPlayer (int player, char *p)
11204 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11205     int i;
11206     char buf[MSG_SIZ], *engineName;
11207     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11208     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11209     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11210     if(mnemonic[i]) {
11211         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11212         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11213         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11214         ParseArgsFromString(buf);
11215     } else { // no engine with this nickname is installed!
11216         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11217         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11218         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11219         ModeHighlight();
11220         DisplayError(buf, 0);
11221         return 0;
11222     }
11223     free(engineName);
11224     return i;
11225 }
11226
11227 char *recentEngines;
11228
11229 void
11230 RecentEngineEvent (int nr)
11231 {
11232     int n;
11233 //    SwapEngines(1); // bump first to second
11234 //    ReplaceEngine(&second, 1); // and load it there
11235     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11236     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11237     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11238         ReplaceEngine(&first, 0);
11239         FloatToFront(&appData.recentEngineList, command[n]);
11240     }
11241 }
11242
11243 int
11244 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11245 {   // determine players from game number
11246     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11247
11248     if(appData.tourneyType == 0) {
11249         roundsPerCycle = (nPlayers - 1) | 1;
11250         pairingsPerRound = nPlayers / 2;
11251     } else if(appData.tourneyType > 0) {
11252         roundsPerCycle = nPlayers - appData.tourneyType;
11253         pairingsPerRound = appData.tourneyType;
11254     }
11255     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11256     gamesPerCycle = gamesPerRound * roundsPerCycle;
11257     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11258     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11259     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11260     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11261     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11262     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11263
11264     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11265     if(appData.roundSync) *syncInterval = gamesPerRound;
11266
11267     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11268
11269     if(appData.tourneyType == 0) {
11270         if(curPairing == (nPlayers-1)/2 ) {
11271             *whitePlayer = curRound;
11272             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11273         } else {
11274             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11275             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11276             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11277             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11278         }
11279     } else if(appData.tourneyType > 1) {
11280         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11281         *whitePlayer = curRound + appData.tourneyType;
11282     } else if(appData.tourneyType > 0) {
11283         *whitePlayer = curPairing;
11284         *blackPlayer = curRound + appData.tourneyType;
11285     }
11286
11287     // take care of white/black alternation per round.
11288     // For cycles and games this is already taken care of by default, derived from matchGame!
11289     return curRound & 1;
11290 }
11291
11292 int
11293 NextTourneyGame (int nr, int *swapColors)
11294 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11295     char *p, *q;
11296     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11297     FILE *tf;
11298     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11299     tf = fopen(appData.tourneyFile, "r");
11300     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11301     ParseArgsFromFile(tf); fclose(tf);
11302     InitTimeControls(); // TC might be altered from tourney file
11303
11304     nPlayers = CountPlayers(appData.participants); // count participants
11305     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11306     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11307
11308     if(syncInterval) {
11309         p = q = appData.results;
11310         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11311         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11312             DisplayMessage(_("Waiting for other game(s)"),"");
11313             waitingForGame = TRUE;
11314             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11315             return 0;
11316         }
11317         waitingForGame = FALSE;
11318     }
11319
11320     if(appData.tourneyType < 0) {
11321         if(nr>=0 && !pairingReceived) {
11322             char buf[1<<16];
11323             if(pairing.pr == NoProc) {
11324                 if(!appData.pairingEngine[0]) {
11325                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11326                     return 0;
11327                 }
11328                 StartChessProgram(&pairing); // starts the pairing engine
11329             }
11330             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11331             SendToProgram(buf, &pairing);
11332             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11333             SendToProgram(buf, &pairing);
11334             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11335         }
11336         pairingReceived = 0;                              // ... so we continue here
11337         *swapColors = 0;
11338         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11339         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11340         matchGame = 1; roundNr = nr / syncInterval + 1;
11341     }
11342
11343     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11344
11345     // redefine engines, engine dir, etc.
11346     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11347     if(first.pr == NoProc) {
11348       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11349       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11350     }
11351     if(second.pr == NoProc) {
11352       SwapEngines(1);
11353       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11354       SwapEngines(1);         // and make that valid for second engine by swapping
11355       InitEngine(&second, 1);
11356     }
11357     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11358     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11359     return OK;
11360 }
11361
11362 void
11363 NextMatchGame ()
11364 {   // performs game initialization that does not invoke engines, and then tries to start the game
11365     int res, firstWhite, swapColors = 0;
11366     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11367     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
11368         char buf[MSG_SIZ];
11369         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11370         if(strcmp(buf, currentDebugFile)) { // name has changed
11371             FILE *f = fopen(buf, "w");
11372             if(f) { // if opening the new file failed, just keep using the old one
11373                 ASSIGN(currentDebugFile, buf);
11374                 fclose(debugFP);
11375                 debugFP = f;
11376             }
11377             if(appData.serverFileName) {
11378                 if(serverFP) fclose(serverFP);
11379                 serverFP = fopen(appData.serverFileName, "w");
11380                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11381                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11382             }
11383         }
11384     }
11385     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11386     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11387     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11388     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11389     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11390     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11391     Reset(FALSE, first.pr != NoProc);
11392     res = LoadGameOrPosition(matchGame); // setup game
11393     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11394     if(!res) return; // abort when bad game/pos file
11395     TwoMachinesEvent();
11396 }
11397
11398 void
11399 UserAdjudicationEvent (int result)
11400 {
11401     ChessMove gameResult = GameIsDrawn;
11402
11403     if( result > 0 ) {
11404         gameResult = WhiteWins;
11405     }
11406     else if( result < 0 ) {
11407         gameResult = BlackWins;
11408     }
11409
11410     if( gameMode == TwoMachinesPlay ) {
11411         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11412     }
11413 }
11414
11415
11416 // [HGM] save: calculate checksum of game to make games easily identifiable
11417 int
11418 StringCheckSum (char *s)
11419 {
11420         int i = 0;
11421         if(s==NULL) return 0;
11422         while(*s) i = i*259 + *s++;
11423         return i;
11424 }
11425
11426 int
11427 GameCheckSum ()
11428 {
11429         int i, sum=0;
11430         for(i=backwardMostMove; i<forwardMostMove; i++) {
11431                 sum += pvInfoList[i].depth;
11432                 sum += StringCheckSum(parseList[i]);
11433                 sum += StringCheckSum(commentList[i]);
11434                 sum *= 261;
11435         }
11436         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11437         return sum + StringCheckSum(commentList[i]);
11438 } // end of save patch
11439
11440 void
11441 GameEnds (ChessMove result, char *resultDetails, int whosays)
11442 {
11443     GameMode nextGameMode;
11444     int isIcsGame;
11445     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11446
11447     if(endingGame) return; /* [HGM] crash: forbid recursion */
11448     endingGame = 1;
11449     if(twoBoards) { // [HGM] dual: switch back to one board
11450         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11451         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11452     }
11453     if (appData.debugMode) {
11454       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11455               result, resultDetails ? resultDetails : "(null)", whosays);
11456     }
11457
11458     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11459
11460     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11461
11462     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11463         /* If we are playing on ICS, the server decides when the
11464            game is over, but the engine can offer to draw, claim
11465            a draw, or resign.
11466          */
11467 #if ZIPPY
11468         if (appData.zippyPlay && first.initDone) {
11469             if (result == GameIsDrawn) {
11470                 /* In case draw still needs to be claimed */
11471                 SendToICS(ics_prefix);
11472                 SendToICS("draw\n");
11473             } else if (StrCaseStr(resultDetails, "resign")) {
11474                 SendToICS(ics_prefix);
11475                 SendToICS("resign\n");
11476             }
11477         }
11478 #endif
11479         endingGame = 0; /* [HGM] crash */
11480         return;
11481     }
11482
11483     /* If we're loading the game from a file, stop */
11484     if (whosays == GE_FILE) {
11485       (void) StopLoadGameTimer();
11486       gameFileFP = NULL;
11487     }
11488
11489     /* Cancel draw offers */
11490     first.offeredDraw = second.offeredDraw = 0;
11491
11492     /* If this is an ICS game, only ICS can really say it's done;
11493        if not, anyone can. */
11494     isIcsGame = (gameMode == IcsPlayingWhite ||
11495                  gameMode == IcsPlayingBlack ||
11496                  gameMode == IcsObserving    ||
11497                  gameMode == IcsExamining);
11498
11499     if (!isIcsGame || whosays == GE_ICS) {
11500         /* OK -- not an ICS game, or ICS said it was done */
11501         StopClocks();
11502         if (!isIcsGame && !appData.noChessProgram)
11503           SetUserThinkingEnables();
11504
11505         /* [HGM] if a machine claims the game end we verify this claim */
11506         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11507             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11508                 char claimer;
11509                 ChessMove trueResult = (ChessMove) -1;
11510
11511                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11512                                             first.twoMachinesColor[0] :
11513                                             second.twoMachinesColor[0] ;
11514
11515                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11516                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11517                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11518                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11519                 } else
11520                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11521                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11522                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11523                 } else
11524                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11525                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11526                 }
11527
11528                 // now verify win claims, but not in drop games, as we don't understand those yet
11529                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11530                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11531                     (result == WhiteWins && claimer == 'w' ||
11532                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11533                       if (appData.debugMode) {
11534                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11535                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11536                       }
11537                       if(result != trueResult) {
11538                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11539                               result = claimer == 'w' ? BlackWins : WhiteWins;
11540                               resultDetails = buf;
11541                       }
11542                 } else
11543                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11544                     && (forwardMostMove <= backwardMostMove ||
11545                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11546                         (claimer=='b')==(forwardMostMove&1))
11547                                                                                   ) {
11548                       /* [HGM] verify: draws that were not flagged are false claims */
11549                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11550                       result = claimer == 'w' ? BlackWins : WhiteWins;
11551                       resultDetails = buf;
11552                 }
11553                 /* (Claiming a loss is accepted no questions asked!) */
11554             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11555                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11556                 result = GameUnfinished;
11557                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11558             }
11559             /* [HGM] bare: don't allow bare King to win */
11560             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11561                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11562                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11563                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11564                && result != GameIsDrawn)
11565             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11566                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11567                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11568                         if(p >= 0 && p <= (int)WhiteKing) k++;
11569                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11570                 }
11571                 if (appData.debugMode) {
11572                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11573                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11574                 }
11575                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11576                         result = GameIsDrawn;
11577                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11578                         resultDetails = buf;
11579                 }
11580             }
11581         }
11582
11583
11584         if(serverMoves != NULL && !loadFlag) { char c = '=';
11585             if(result==WhiteWins) c = '+';
11586             if(result==BlackWins) c = '-';
11587             if(resultDetails != NULL)
11588                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11589         }
11590         if (resultDetails != NULL) {
11591             gameInfo.result = result;
11592             gameInfo.resultDetails = StrSave(resultDetails);
11593
11594             /* display last move only if game was not loaded from file */
11595             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11596                 DisplayMove(currentMove - 1);
11597
11598             if (forwardMostMove != 0) {
11599                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11600                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11601                                                                 ) {
11602                     if (*appData.saveGameFile != NULLCHAR) {
11603                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11604                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11605                         else
11606                         SaveGameToFile(appData.saveGameFile, TRUE);
11607                     } else if (appData.autoSaveGames) {
11608                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11609                     }
11610                     if (*appData.savePositionFile != NULLCHAR) {
11611                         SavePositionToFile(appData.savePositionFile);
11612                     }
11613                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11614                 }
11615             }
11616
11617             /* Tell program how game ended in case it is learning */
11618             /* [HGM] Moved this to after saving the PGN, just in case */
11619             /* engine died and we got here through time loss. In that */
11620             /* case we will get a fatal error writing the pipe, which */
11621             /* would otherwise lose us the PGN.                       */
11622             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11623             /* output during GameEnds should never be fatal anymore   */
11624             if (gameMode == MachinePlaysWhite ||
11625                 gameMode == MachinePlaysBlack ||
11626                 gameMode == TwoMachinesPlay ||
11627                 gameMode == IcsPlayingWhite ||
11628                 gameMode == IcsPlayingBlack ||
11629                 gameMode == BeginningOfGame) {
11630                 char buf[MSG_SIZ];
11631                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11632                         resultDetails);
11633                 if (first.pr != NoProc) {
11634                     SendToProgram(buf, &first);
11635                 }
11636                 if (second.pr != NoProc &&
11637                     gameMode == TwoMachinesPlay) {
11638                     SendToProgram(buf, &second);
11639                 }
11640             }
11641         }
11642
11643         if (appData.icsActive) {
11644             if (appData.quietPlay &&
11645                 (gameMode == IcsPlayingWhite ||
11646                  gameMode == IcsPlayingBlack)) {
11647                 SendToICS(ics_prefix);
11648                 SendToICS("set shout 1\n");
11649             }
11650             nextGameMode = IcsIdle;
11651             ics_user_moved = FALSE;
11652             /* clean up premove.  It's ugly when the game has ended and the
11653              * premove highlights are still on the board.
11654              */
11655             if (gotPremove) {
11656               gotPremove = FALSE;
11657               ClearPremoveHighlights();
11658               DrawPosition(FALSE, boards[currentMove]);
11659             }
11660             if (whosays == GE_ICS) {
11661                 switch (result) {
11662                 case WhiteWins:
11663                     if (gameMode == IcsPlayingWhite)
11664                         PlayIcsWinSound();
11665                     else if(gameMode == IcsPlayingBlack)
11666                         PlayIcsLossSound();
11667                     break;
11668                 case BlackWins:
11669                     if (gameMode == IcsPlayingBlack)
11670                         PlayIcsWinSound();
11671                     else if(gameMode == IcsPlayingWhite)
11672                         PlayIcsLossSound();
11673                     break;
11674                 case GameIsDrawn:
11675                     PlayIcsDrawSound();
11676                     break;
11677                 default:
11678                     PlayIcsUnfinishedSound();
11679                 }
11680             }
11681             if(appData.quitNext) { ExitEvent(0); return; }
11682         } else if (gameMode == EditGame ||
11683                    gameMode == PlayFromGameFile ||
11684                    gameMode == AnalyzeMode ||
11685                    gameMode == AnalyzeFile) {
11686             nextGameMode = gameMode;
11687         } else {
11688             nextGameMode = EndOfGame;
11689         }
11690         pausing = FALSE;
11691         ModeHighlight();
11692     } else {
11693         nextGameMode = gameMode;
11694     }
11695
11696     if (appData.noChessProgram) {
11697         gameMode = nextGameMode;
11698         ModeHighlight();
11699         endingGame = 0; /* [HGM] crash */
11700         return;
11701     }
11702
11703     if (first.reuse) {
11704         /* Put first chess program into idle state */
11705         if (first.pr != NoProc &&
11706             (gameMode == MachinePlaysWhite ||
11707              gameMode == MachinePlaysBlack ||
11708              gameMode == TwoMachinesPlay ||
11709              gameMode == IcsPlayingWhite ||
11710              gameMode == IcsPlayingBlack ||
11711              gameMode == BeginningOfGame)) {
11712             SendToProgram("force\n", &first);
11713             if (first.usePing) {
11714               char buf[MSG_SIZ];
11715               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11716               SendToProgram(buf, &first);
11717             }
11718         }
11719     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11720         /* Kill off first chess program */
11721         if (first.isr != NULL)
11722           RemoveInputSource(first.isr);
11723         first.isr = NULL;
11724
11725         if (first.pr != NoProc) {
11726             ExitAnalyzeMode();
11727             DoSleep( appData.delayBeforeQuit );
11728             SendToProgram("quit\n", &first);
11729             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11730             first.reload = TRUE;
11731         }
11732         first.pr = NoProc;
11733     }
11734     if (second.reuse) {
11735         /* Put second chess program into idle state */
11736         if (second.pr != NoProc &&
11737             gameMode == TwoMachinesPlay) {
11738             SendToProgram("force\n", &second);
11739             if (second.usePing) {
11740               char buf[MSG_SIZ];
11741               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11742               SendToProgram(buf, &second);
11743             }
11744         }
11745     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11746         /* Kill off second chess program */
11747         if (second.isr != NULL)
11748           RemoveInputSource(second.isr);
11749         second.isr = NULL;
11750
11751         if (second.pr != NoProc) {
11752             DoSleep( appData.delayBeforeQuit );
11753             SendToProgram("quit\n", &second);
11754             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11755             second.reload = TRUE;
11756         }
11757         second.pr = NoProc;
11758     }
11759
11760     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11761         char resChar = '=';
11762         switch (result) {
11763         case WhiteWins:
11764           resChar = '+';
11765           if (first.twoMachinesColor[0] == 'w') {
11766             first.matchWins++;
11767           } else {
11768             second.matchWins++;
11769           }
11770           break;
11771         case BlackWins:
11772           resChar = '-';
11773           if (first.twoMachinesColor[0] == 'b') {
11774             first.matchWins++;
11775           } else {
11776             second.matchWins++;
11777           }
11778           break;
11779         case GameUnfinished:
11780           resChar = ' ';
11781         default:
11782           break;
11783         }
11784
11785         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11786         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11787             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11788             ReserveGame(nextGame, resChar); // sets nextGame
11789             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11790             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11791         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11792
11793         if (nextGame <= appData.matchGames && !abortMatch) {
11794             gameMode = nextGameMode;
11795             matchGame = nextGame; // this will be overruled in tourney mode!
11796             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11797             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11798             endingGame = 0; /* [HGM] crash */
11799             return;
11800         } else {
11801             gameMode = nextGameMode;
11802             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11803                      first.tidy, second.tidy,
11804                      first.matchWins, second.matchWins,
11805                      appData.matchGames - (first.matchWins + second.matchWins));
11806             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11807             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11808             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11809             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11810                 first.twoMachinesColor = "black\n";
11811                 second.twoMachinesColor = "white\n";
11812             } else {
11813                 first.twoMachinesColor = "white\n";
11814                 second.twoMachinesColor = "black\n";
11815             }
11816         }
11817     }
11818     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11819         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11820       ExitAnalyzeMode();
11821     gameMode = nextGameMode;
11822     ModeHighlight();
11823     endingGame = 0;  /* [HGM] crash */
11824     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11825         if(matchMode == TRUE) { // match through command line: exit with or without popup
11826             if(ranking) {
11827                 ToNrEvent(forwardMostMove);
11828                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11829                 else ExitEvent(0);
11830             } else DisplayFatalError(buf, 0, 0);
11831         } else { // match through menu; just stop, with or without popup
11832             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11833             ModeHighlight();
11834             if(ranking){
11835                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11836             } else DisplayNote(buf);
11837       }
11838       if(ranking) free(ranking);
11839     }
11840 }
11841
11842 /* Assumes program was just initialized (initString sent).
11843    Leaves program in force mode. */
11844 void
11845 FeedMovesToProgram (ChessProgramState *cps, int upto)
11846 {
11847     int i;
11848
11849     if (appData.debugMode)
11850       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11851               startedFromSetupPosition ? "position and " : "",
11852               backwardMostMove, upto, cps->which);
11853     if(currentlyInitializedVariant != gameInfo.variant) {
11854       char buf[MSG_SIZ];
11855         // [HGM] variantswitch: make engine aware of new variant
11856         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11857                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11858                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11859         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11860         SendToProgram(buf, cps);
11861         currentlyInitializedVariant = gameInfo.variant;
11862     }
11863     SendToProgram("force\n", cps);
11864     if (startedFromSetupPosition) {
11865         SendBoard(cps, backwardMostMove);
11866     if (appData.debugMode) {
11867         fprintf(debugFP, "feedMoves\n");
11868     }
11869     }
11870     for (i = backwardMostMove; i < upto; i++) {
11871         SendMoveToProgram(i, cps);
11872     }
11873 }
11874
11875
11876 int
11877 ResurrectChessProgram ()
11878 {
11879      /* The chess program may have exited.
11880         If so, restart it and feed it all the moves made so far. */
11881     static int doInit = 0;
11882
11883     if (appData.noChessProgram) return 1;
11884
11885     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11886         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11887         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11888         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11889     } else {
11890         if (first.pr != NoProc) return 1;
11891         StartChessProgram(&first);
11892     }
11893     InitChessProgram(&first, FALSE);
11894     FeedMovesToProgram(&first, currentMove);
11895
11896     if (!first.sendTime) {
11897         /* can't tell gnuchess what its clock should read,
11898            so we bow to its notion. */
11899         ResetClocks();
11900         timeRemaining[0][currentMove] = whiteTimeRemaining;
11901         timeRemaining[1][currentMove] = blackTimeRemaining;
11902     }
11903
11904     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11905                 appData.icsEngineAnalyze) && first.analysisSupport) {
11906       SendToProgram("analyze\n", &first);
11907       first.analyzing = TRUE;
11908     }
11909     return 1;
11910 }
11911
11912 /*
11913  * Button procedures
11914  */
11915 void
11916 Reset (int redraw, int init)
11917 {
11918     int i;
11919
11920     if (appData.debugMode) {
11921         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11922                 redraw, init, gameMode);
11923     }
11924     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11925     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11926     CleanupTail(); // [HGM] vari: delete any stored variations
11927     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11928     pausing = pauseExamInvalid = FALSE;
11929     startedFromSetupPosition = blackPlaysFirst = FALSE;
11930     firstMove = TRUE;
11931     whiteFlag = blackFlag = FALSE;
11932     userOfferedDraw = FALSE;
11933     hintRequested = bookRequested = FALSE;
11934     first.maybeThinking = FALSE;
11935     second.maybeThinking = FALSE;
11936     first.bookSuspend = FALSE; // [HGM] book
11937     second.bookSuspend = FALSE;
11938     thinkOutput[0] = NULLCHAR;
11939     lastHint[0] = NULLCHAR;
11940     ClearGameInfo(&gameInfo);
11941     gameInfo.variant = StringToVariant(appData.variant);
11942     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11943     ics_user_moved = ics_clock_paused = FALSE;
11944     ics_getting_history = H_FALSE;
11945     ics_gamenum = -1;
11946     white_holding[0] = black_holding[0] = NULLCHAR;
11947     ClearProgramStats();
11948     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11949
11950     ResetFrontEnd();
11951     ClearHighlights();
11952     flipView = appData.flipView;
11953     ClearPremoveHighlights();
11954     gotPremove = FALSE;
11955     alarmSounded = FALSE;
11956     killX = killY = -1; // [HGM] lion
11957
11958     GameEnds(EndOfFile, NULL, GE_PLAYER);
11959     if(appData.serverMovesName != NULL) {
11960         /* [HGM] prepare to make moves file for broadcasting */
11961         clock_t t = clock();
11962         if(serverMoves != NULL) fclose(serverMoves);
11963         serverMoves = fopen(appData.serverMovesName, "r");
11964         if(serverMoves != NULL) {
11965             fclose(serverMoves);
11966             /* delay 15 sec before overwriting, so all clients can see end */
11967             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11968         }
11969         serverMoves = fopen(appData.serverMovesName, "w");
11970     }
11971
11972     ExitAnalyzeMode();
11973     gameMode = BeginningOfGame;
11974     ModeHighlight();
11975     if(appData.icsActive) gameInfo.variant = VariantNormal;
11976     currentMove = forwardMostMove = backwardMostMove = 0;
11977     MarkTargetSquares(1);
11978     InitPosition(redraw);
11979     for (i = 0; i < MAX_MOVES; i++) {
11980         if (commentList[i] != NULL) {
11981             free(commentList[i]);
11982             commentList[i] = NULL;
11983         }
11984     }
11985     ResetClocks();
11986     timeRemaining[0][0] = whiteTimeRemaining;
11987     timeRemaining[1][0] = blackTimeRemaining;
11988
11989     if (first.pr == NoProc) {
11990         StartChessProgram(&first);
11991     }
11992     if (init) {
11993             InitChessProgram(&first, startedFromSetupPosition);
11994     }
11995     DisplayTitle("");
11996     DisplayMessage("", "");
11997     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11998     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11999     ClearMap();        // [HGM] exclude: invalidate map
12000 }
12001
12002 void
12003 AutoPlayGameLoop ()
12004 {
12005     for (;;) {
12006         if (!AutoPlayOneMove())
12007           return;
12008         if (matchMode || appData.timeDelay == 0)
12009           continue;
12010         if (appData.timeDelay < 0)
12011           return;
12012         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12013         break;
12014     }
12015 }
12016
12017 void
12018 AnalyzeNextGame()
12019 {
12020     ReloadGame(1); // next game
12021 }
12022
12023 int
12024 AutoPlayOneMove ()
12025 {
12026     int fromX, fromY, toX, toY;
12027
12028     if (appData.debugMode) {
12029       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12030     }
12031
12032     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12033       return FALSE;
12034
12035     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12036       pvInfoList[currentMove].depth = programStats.depth;
12037       pvInfoList[currentMove].score = programStats.score;
12038       pvInfoList[currentMove].time  = 0;
12039       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12040       else { // append analysis of final position as comment
12041         char buf[MSG_SIZ];
12042         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12043         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12044       }
12045       programStats.depth = 0;
12046     }
12047
12048     if (currentMove >= forwardMostMove) {
12049       if(gameMode == AnalyzeFile) {
12050           if(appData.loadGameIndex == -1) {
12051             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12052           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12053           } else {
12054           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12055         }
12056       }
12057 //      gameMode = EndOfGame;
12058 //      ModeHighlight();
12059
12060       /* [AS] Clear current move marker at the end of a game */
12061       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12062
12063       return FALSE;
12064     }
12065
12066     toX = moveList[currentMove][2] - AAA;
12067     toY = moveList[currentMove][3] - ONE;
12068
12069     if (moveList[currentMove][1] == '@') {
12070         if (appData.highlightLastMove) {
12071             SetHighlights(-1, -1, toX, toY);
12072         }
12073     } else {
12074         int viaX = moveList[currentMove][5] - AAA;
12075         int viaY = moveList[currentMove][6] - ONE;
12076         fromX = moveList[currentMove][0] - AAA;
12077         fromY = moveList[currentMove][1] - ONE;
12078
12079         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12080
12081         if(moveList[currentMove][4] == ';') { // multi-leg
12082             ChessSquare piece = boards[currentMove][viaY][viaX];
12083             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12084             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12085             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12086             boards[currentMove][viaY][viaX] = piece;
12087         } else
12088         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12089
12090         if (appData.highlightLastMove) {
12091             SetHighlights(fromX, fromY, toX, toY);
12092         }
12093     }
12094     DisplayMove(currentMove);
12095     SendMoveToProgram(currentMove++, &first);
12096     DisplayBothClocks();
12097     DrawPosition(FALSE, boards[currentMove]);
12098     // [HGM] PV info: always display, routine tests if empty
12099     DisplayComment(currentMove - 1, commentList[currentMove]);
12100     return TRUE;
12101 }
12102
12103
12104 int
12105 LoadGameOneMove (ChessMove readAhead)
12106 {
12107     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12108     char promoChar = NULLCHAR;
12109     ChessMove moveType;
12110     char move[MSG_SIZ];
12111     char *p, *q;
12112
12113     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12114         gameMode != AnalyzeMode && gameMode != Training) {
12115         gameFileFP = NULL;
12116         return FALSE;
12117     }
12118
12119     yyboardindex = forwardMostMove;
12120     if (readAhead != EndOfFile) {
12121       moveType = readAhead;
12122     } else {
12123       if (gameFileFP == NULL)
12124           return FALSE;
12125       moveType = (ChessMove) Myylex();
12126     }
12127
12128     done = FALSE;
12129     switch (moveType) {
12130       case Comment:
12131         if (appData.debugMode)
12132           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12133         p = yy_text;
12134
12135         /* append the comment but don't display it */
12136         AppendComment(currentMove, p, FALSE);
12137         return TRUE;
12138
12139       case WhiteCapturesEnPassant:
12140       case BlackCapturesEnPassant:
12141       case WhitePromotion:
12142       case BlackPromotion:
12143       case WhiteNonPromotion:
12144       case BlackNonPromotion:
12145       case NormalMove:
12146       case FirstLeg:
12147       case WhiteKingSideCastle:
12148       case WhiteQueenSideCastle:
12149       case BlackKingSideCastle:
12150       case BlackQueenSideCastle:
12151       case WhiteKingSideCastleWild:
12152       case WhiteQueenSideCastleWild:
12153       case BlackKingSideCastleWild:
12154       case BlackQueenSideCastleWild:
12155       /* PUSH Fabien */
12156       case WhiteHSideCastleFR:
12157       case WhiteASideCastleFR:
12158       case BlackHSideCastleFR:
12159       case BlackASideCastleFR:
12160       /* POP Fabien */
12161         if (appData.debugMode)
12162           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12163         fromX = currentMoveString[0] - AAA;
12164         fromY = currentMoveString[1] - ONE;
12165         toX = currentMoveString[2] - AAA;
12166         toY = currentMoveString[3] - ONE;
12167         promoChar = currentMoveString[4];
12168         if(promoChar == ';') promoChar = currentMoveString[7];
12169         break;
12170
12171       case WhiteDrop:
12172       case BlackDrop:
12173         if (appData.debugMode)
12174           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12175         fromX = moveType == WhiteDrop ?
12176           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12177         (int) CharToPiece(ToLower(currentMoveString[0]));
12178         fromY = DROP_RANK;
12179         toX = currentMoveString[2] - AAA;
12180         toY = currentMoveString[3] - ONE;
12181         break;
12182
12183       case WhiteWins:
12184       case BlackWins:
12185       case GameIsDrawn:
12186       case GameUnfinished:
12187         if (appData.debugMode)
12188           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12189         p = strchr(yy_text, '{');
12190         if (p == NULL) p = strchr(yy_text, '(');
12191         if (p == NULL) {
12192             p = yy_text;
12193             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12194         } else {
12195             q = strchr(p, *p == '{' ? '}' : ')');
12196             if (q != NULL) *q = NULLCHAR;
12197             p++;
12198         }
12199         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12200         GameEnds(moveType, p, GE_FILE);
12201         done = TRUE;
12202         if (cmailMsgLoaded) {
12203             ClearHighlights();
12204             flipView = WhiteOnMove(currentMove);
12205             if (moveType == GameUnfinished) flipView = !flipView;
12206             if (appData.debugMode)
12207               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12208         }
12209         break;
12210
12211       case EndOfFile:
12212         if (appData.debugMode)
12213           fprintf(debugFP, "Parser hit end of file\n");
12214         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12215           case MT_NONE:
12216           case MT_CHECK:
12217             break;
12218           case MT_CHECKMATE:
12219           case MT_STAINMATE:
12220             if (WhiteOnMove(currentMove)) {
12221                 GameEnds(BlackWins, "Black mates", GE_FILE);
12222             } else {
12223                 GameEnds(WhiteWins, "White mates", GE_FILE);
12224             }
12225             break;
12226           case MT_STALEMATE:
12227             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12228             break;
12229         }
12230         done = TRUE;
12231         break;
12232
12233       case MoveNumberOne:
12234         if (lastLoadGameStart == GNUChessGame) {
12235             /* GNUChessGames have numbers, but they aren't move numbers */
12236             if (appData.debugMode)
12237               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12238                       yy_text, (int) moveType);
12239             return LoadGameOneMove(EndOfFile); /* tail recursion */
12240         }
12241         /* else fall thru */
12242
12243       case XBoardGame:
12244       case GNUChessGame:
12245       case PGNTag:
12246         /* Reached start of next game in file */
12247         if (appData.debugMode)
12248           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12249         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12250           case MT_NONE:
12251           case MT_CHECK:
12252             break;
12253           case MT_CHECKMATE:
12254           case MT_STAINMATE:
12255             if (WhiteOnMove(currentMove)) {
12256                 GameEnds(BlackWins, "Black mates", GE_FILE);
12257             } else {
12258                 GameEnds(WhiteWins, "White mates", GE_FILE);
12259             }
12260             break;
12261           case MT_STALEMATE:
12262             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12263             break;
12264         }
12265         done = TRUE;
12266         break;
12267
12268       case PositionDiagram:     /* should not happen; ignore */
12269       case ElapsedTime:         /* ignore */
12270       case NAG:                 /* ignore */
12271         if (appData.debugMode)
12272           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12273                   yy_text, (int) moveType);
12274         return LoadGameOneMove(EndOfFile); /* tail recursion */
12275
12276       case IllegalMove:
12277         if (appData.testLegality) {
12278             if (appData.debugMode)
12279               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12280             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12281                     (forwardMostMove / 2) + 1,
12282                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12283             DisplayError(move, 0);
12284             done = TRUE;
12285         } else {
12286             if (appData.debugMode)
12287               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12288                       yy_text, currentMoveString);
12289             if(currentMoveString[1] == '@') {
12290                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12291                 fromY = DROP_RANK;
12292             } else {
12293                 fromX = currentMoveString[0] - AAA;
12294                 fromY = currentMoveString[1] - ONE;
12295             }
12296             toX = currentMoveString[2] - AAA;
12297             toY = currentMoveString[3] - ONE;
12298             promoChar = currentMoveString[4];
12299         }
12300         break;
12301
12302       case AmbiguousMove:
12303         if (appData.debugMode)
12304           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12305         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12306                 (forwardMostMove / 2) + 1,
12307                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12308         DisplayError(move, 0);
12309         done = TRUE;
12310         break;
12311
12312       default:
12313       case ImpossibleMove:
12314         if (appData.debugMode)
12315           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12316         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12317                 (forwardMostMove / 2) + 1,
12318                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12319         DisplayError(move, 0);
12320         done = TRUE;
12321         break;
12322     }
12323
12324     if (done) {
12325         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12326             DrawPosition(FALSE, boards[currentMove]);
12327             DisplayBothClocks();
12328             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12329               DisplayComment(currentMove - 1, commentList[currentMove]);
12330         }
12331         (void) StopLoadGameTimer();
12332         gameFileFP = NULL;
12333         cmailOldMove = forwardMostMove;
12334         return FALSE;
12335     } else {
12336         /* currentMoveString is set as a side-effect of yylex */
12337
12338         thinkOutput[0] = NULLCHAR;
12339         MakeMove(fromX, fromY, toX, toY, promoChar);
12340         killX = killY = -1; // [HGM] lion: used up
12341         currentMove = forwardMostMove;
12342         return TRUE;
12343     }
12344 }
12345
12346 /* Load the nth game from the given file */
12347 int
12348 LoadGameFromFile (char *filename, int n, char *title, int useList)
12349 {
12350     FILE *f;
12351     char buf[MSG_SIZ];
12352
12353     if (strcmp(filename, "-") == 0) {
12354         f = stdin;
12355         title = "stdin";
12356     } else {
12357         f = fopen(filename, "rb");
12358         if (f == NULL) {
12359           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12360             DisplayError(buf, errno);
12361             return FALSE;
12362         }
12363     }
12364     if (fseek(f, 0, 0) == -1) {
12365         /* f is not seekable; probably a pipe */
12366         useList = FALSE;
12367     }
12368     if (useList && n == 0) {
12369         int error = GameListBuild(f);
12370         if (error) {
12371             DisplayError(_("Cannot build game list"), error);
12372         } else if (!ListEmpty(&gameList) &&
12373                    ((ListGame *) gameList.tailPred)->number > 1) {
12374             GameListPopUp(f, title);
12375             return TRUE;
12376         }
12377         GameListDestroy();
12378         n = 1;
12379     }
12380     if (n == 0) n = 1;
12381     return LoadGame(f, n, title, FALSE);
12382 }
12383
12384
12385 void
12386 MakeRegisteredMove ()
12387 {
12388     int fromX, fromY, toX, toY;
12389     char promoChar;
12390     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12391         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12392           case CMAIL_MOVE:
12393           case CMAIL_DRAW:
12394             if (appData.debugMode)
12395               fprintf(debugFP, "Restoring %s for game %d\n",
12396                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12397
12398             thinkOutput[0] = NULLCHAR;
12399             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12400             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12401             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12402             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12403             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12404             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12405             MakeMove(fromX, fromY, toX, toY, promoChar);
12406             ShowMove(fromX, fromY, toX, toY);
12407
12408             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12409               case MT_NONE:
12410               case MT_CHECK:
12411                 break;
12412
12413               case MT_CHECKMATE:
12414               case MT_STAINMATE:
12415                 if (WhiteOnMove(currentMove)) {
12416                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12417                 } else {
12418                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12419                 }
12420                 break;
12421
12422               case MT_STALEMATE:
12423                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12424                 break;
12425             }
12426
12427             break;
12428
12429           case CMAIL_RESIGN:
12430             if (WhiteOnMove(currentMove)) {
12431                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12432             } else {
12433                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12434             }
12435             break;
12436
12437           case CMAIL_ACCEPT:
12438             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12439             break;
12440
12441           default:
12442             break;
12443         }
12444     }
12445
12446     return;
12447 }
12448
12449 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12450 int
12451 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12452 {
12453     int retVal;
12454
12455     if (gameNumber > nCmailGames) {
12456         DisplayError(_("No more games in this message"), 0);
12457         return FALSE;
12458     }
12459     if (f == lastLoadGameFP) {
12460         int offset = gameNumber - lastLoadGameNumber;
12461         if (offset == 0) {
12462             cmailMsg[0] = NULLCHAR;
12463             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12464                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12465                 nCmailMovesRegistered--;
12466             }
12467             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12468             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12469                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12470             }
12471         } else {
12472             if (! RegisterMove()) return FALSE;
12473         }
12474     }
12475
12476     retVal = LoadGame(f, gameNumber, title, useList);
12477
12478     /* Make move registered during previous look at this game, if any */
12479     MakeRegisteredMove();
12480
12481     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12482         commentList[currentMove]
12483           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12484         DisplayComment(currentMove - 1, commentList[currentMove]);
12485     }
12486
12487     return retVal;
12488 }
12489
12490 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12491 int
12492 ReloadGame (int offset)
12493 {
12494     int gameNumber = lastLoadGameNumber + offset;
12495     if (lastLoadGameFP == NULL) {
12496         DisplayError(_("No game has been loaded yet"), 0);
12497         return FALSE;
12498     }
12499     if (gameNumber <= 0) {
12500         DisplayError(_("Can't back up any further"), 0);
12501         return FALSE;
12502     }
12503     if (cmailMsgLoaded) {
12504         return CmailLoadGame(lastLoadGameFP, gameNumber,
12505                              lastLoadGameTitle, lastLoadGameUseList);
12506     } else {
12507         return LoadGame(lastLoadGameFP, gameNumber,
12508                         lastLoadGameTitle, lastLoadGameUseList);
12509     }
12510 }
12511
12512 int keys[EmptySquare+1];
12513
12514 int
12515 PositionMatches (Board b1, Board b2)
12516 {
12517     int r, f, sum=0;
12518     switch(appData.searchMode) {
12519         case 1: return CompareWithRights(b1, b2);
12520         case 2:
12521             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12522                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12523             }
12524             return TRUE;
12525         case 3:
12526             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12527               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12528                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12529             }
12530             return sum==0;
12531         case 4:
12532             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12533                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12534             }
12535             return sum==0;
12536     }
12537     return TRUE;
12538 }
12539
12540 #define Q_PROMO  4
12541 #define Q_EP     3
12542 #define Q_BCASTL 2
12543 #define Q_WCASTL 1
12544
12545 int pieceList[256], quickBoard[256];
12546 ChessSquare pieceType[256] = { EmptySquare };
12547 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12548 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12549 int soughtTotal, turn;
12550 Boolean epOK, flipSearch;
12551
12552 typedef struct {
12553     unsigned char piece, to;
12554 } Move;
12555
12556 #define DSIZE (250000)
12557
12558 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12559 Move *moveDatabase = initialSpace;
12560 unsigned int movePtr, dataSize = DSIZE;
12561
12562 int
12563 MakePieceList (Board board, int *counts)
12564 {
12565     int r, f, n=Q_PROMO, total=0;
12566     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12567     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12568         int sq = f + (r<<4);
12569         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12570             quickBoard[sq] = ++n;
12571             pieceList[n] = sq;
12572             pieceType[n] = board[r][f];
12573             counts[board[r][f]]++;
12574             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12575             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12576             total++;
12577         }
12578     }
12579     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12580     return total;
12581 }
12582
12583 void
12584 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12585 {
12586     int sq = fromX + (fromY<<4);
12587     int piece = quickBoard[sq], rook;
12588     quickBoard[sq] = 0;
12589     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12590     if(piece == pieceList[1] && fromY == toY) {
12591       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12592         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12593         moveDatabase[movePtr++].piece = Q_WCASTL;
12594         quickBoard[sq] = piece;
12595         piece = quickBoard[from]; quickBoard[from] = 0;
12596         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12597       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12598         quickBoard[sq] = 0; // remove Rook
12599         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12600         moveDatabase[movePtr++].piece = Q_WCASTL;
12601         quickBoard[sq] = pieceList[1]; // put King
12602         piece = rook;
12603         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12604       }
12605     } else
12606     if(piece == pieceList[2] && fromY == toY) {
12607       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12608         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12609         moveDatabase[movePtr++].piece = Q_BCASTL;
12610         quickBoard[sq] = piece;
12611         piece = quickBoard[from]; quickBoard[from] = 0;
12612         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12613       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12614         quickBoard[sq] = 0; // remove Rook
12615         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12616         moveDatabase[movePtr++].piece = Q_BCASTL;
12617         quickBoard[sq] = pieceList[2]; // put King
12618         piece = rook;
12619         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12620       }
12621     } else
12622     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12623         quickBoard[(fromY<<4)+toX] = 0;
12624         moveDatabase[movePtr].piece = Q_EP;
12625         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12626         moveDatabase[movePtr].to = sq;
12627     } else
12628     if(promoPiece != pieceType[piece]) {
12629         moveDatabase[movePtr++].piece = Q_PROMO;
12630         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12631     }
12632     moveDatabase[movePtr].piece = piece;
12633     quickBoard[sq] = piece;
12634     movePtr++;
12635 }
12636
12637 int
12638 PackGame (Board board)
12639 {
12640     Move *newSpace = NULL;
12641     moveDatabase[movePtr].piece = 0; // terminate previous game
12642     if(movePtr > dataSize) {
12643         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12644         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12645         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12646         if(newSpace) {
12647             int i;
12648             Move *p = moveDatabase, *q = newSpace;
12649             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12650             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12651             moveDatabase = newSpace;
12652         } else { // calloc failed, we must be out of memory. Too bad...
12653             dataSize = 0; // prevent calloc events for all subsequent games
12654             return 0;     // and signal this one isn't cached
12655         }
12656     }
12657     movePtr++;
12658     MakePieceList(board, counts);
12659     return movePtr;
12660 }
12661
12662 int
12663 QuickCompare (Board board, int *minCounts, int *maxCounts)
12664 {   // compare according to search mode
12665     int r, f;
12666     switch(appData.searchMode)
12667     {
12668       case 1: // exact position match
12669         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12670         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12671             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12672         }
12673         break;
12674       case 2: // can have extra material on empty squares
12675         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12676             if(board[r][f] == EmptySquare) continue;
12677             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12678         }
12679         break;
12680       case 3: // material with exact Pawn structure
12681         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12682             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12683             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12684         } // fall through to material comparison
12685       case 4: // exact material
12686         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12687         break;
12688       case 6: // material range with given imbalance
12689         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12690         // fall through to range comparison
12691       case 5: // material range
12692         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12693     }
12694     return TRUE;
12695 }
12696
12697 int
12698 QuickScan (Board board, Move *move)
12699 {   // reconstruct game,and compare all positions in it
12700     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12701     do {
12702         int piece = move->piece;
12703         int to = move->to, from = pieceList[piece];
12704         if(found < 0) { // if already found just scan to game end for final piece count
12705           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12706            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12707            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12708                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12709             ) {
12710             static int lastCounts[EmptySquare+1];
12711             int i;
12712             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12713             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12714           } else stretch = 0;
12715           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12716           if(found >= 0 && !appData.minPieces) return found;
12717         }
12718         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12719           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12720           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12721             piece = (++move)->piece;
12722             from = pieceList[piece];
12723             counts[pieceType[piece]]--;
12724             pieceType[piece] = (ChessSquare) move->to;
12725             counts[move->to]++;
12726           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12727             counts[pieceType[quickBoard[to]]]--;
12728             quickBoard[to] = 0; total--;
12729             move++;
12730             continue;
12731           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12732             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12733             from  = pieceList[piece]; // so this must be King
12734             quickBoard[from] = 0;
12735             pieceList[piece] = to;
12736             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12737             quickBoard[from] = 0; // rook
12738             quickBoard[to] = piece;
12739             to = move->to; piece = move->piece;
12740             goto aftercastle;
12741           }
12742         }
12743         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12744         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12745         quickBoard[from] = 0;
12746       aftercastle:
12747         quickBoard[to] = piece;
12748         pieceList[piece] = to;
12749         cnt++; turn ^= 3;
12750         move++;
12751     } while(1);
12752 }
12753
12754 void
12755 InitSearch ()
12756 {
12757     int r, f;
12758     flipSearch = FALSE;
12759     CopyBoard(soughtBoard, boards[currentMove]);
12760     soughtTotal = MakePieceList(soughtBoard, maxSought);
12761     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12762     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12763     CopyBoard(reverseBoard, boards[currentMove]);
12764     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12765         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12766         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12767         reverseBoard[r][f] = piece;
12768     }
12769     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12770     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12771     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12772                  || (boards[currentMove][CASTLING][2] == NoRights ||
12773                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12774                  && (boards[currentMove][CASTLING][5] == NoRights ||
12775                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12776       ) {
12777         flipSearch = TRUE;
12778         CopyBoard(flipBoard, soughtBoard);
12779         CopyBoard(rotateBoard, reverseBoard);
12780         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12781             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12782             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12783         }
12784     }
12785     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12786     if(appData.searchMode >= 5) {
12787         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12788         MakePieceList(soughtBoard, minSought);
12789         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12790     }
12791     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12792         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12793 }
12794
12795 GameInfo dummyInfo;
12796 static int creatingBook;
12797
12798 int
12799 GameContainsPosition (FILE *f, ListGame *lg)
12800 {
12801     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12802     int fromX, fromY, toX, toY;
12803     char promoChar;
12804     static int initDone=FALSE;
12805
12806     // weed out games based on numerical tag comparison
12807     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12808     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12809     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12810     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12811     if(!initDone) {
12812         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12813         initDone = TRUE;
12814     }
12815     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12816     else CopyBoard(boards[scratch], initialPosition); // default start position
12817     if(lg->moves) {
12818         turn = btm + 1;
12819         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12820         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12821     }
12822     if(btm) plyNr++;
12823     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12824     fseek(f, lg->offset, 0);
12825     yynewfile(f);
12826     while(1) {
12827         yyboardindex = scratch;
12828         quickFlag = plyNr+1;
12829         next = Myylex();
12830         quickFlag = 0;
12831         switch(next) {
12832             case PGNTag:
12833                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12834             default:
12835                 continue;
12836
12837             case XBoardGame:
12838             case GNUChessGame:
12839                 if(plyNr) return -1; // after we have seen moves, this is for new game
12840               continue;
12841
12842             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12843             case ImpossibleMove:
12844             case WhiteWins: // game ends here with these four
12845             case BlackWins:
12846             case GameIsDrawn:
12847             case GameUnfinished:
12848                 return -1;
12849
12850             case IllegalMove:
12851                 if(appData.testLegality) return -1;
12852             case WhiteCapturesEnPassant:
12853             case BlackCapturesEnPassant:
12854             case WhitePromotion:
12855             case BlackPromotion:
12856             case WhiteNonPromotion:
12857             case BlackNonPromotion:
12858             case NormalMove:
12859             case FirstLeg:
12860             case WhiteKingSideCastle:
12861             case WhiteQueenSideCastle:
12862             case BlackKingSideCastle:
12863             case BlackQueenSideCastle:
12864             case WhiteKingSideCastleWild:
12865             case WhiteQueenSideCastleWild:
12866             case BlackKingSideCastleWild:
12867             case BlackQueenSideCastleWild:
12868             case WhiteHSideCastleFR:
12869             case WhiteASideCastleFR:
12870             case BlackHSideCastleFR:
12871             case BlackASideCastleFR:
12872                 fromX = currentMoveString[0] - AAA;
12873                 fromY = currentMoveString[1] - ONE;
12874                 toX = currentMoveString[2] - AAA;
12875                 toY = currentMoveString[3] - ONE;
12876                 promoChar = currentMoveString[4];
12877                 break;
12878             case WhiteDrop:
12879             case BlackDrop:
12880                 fromX = next == WhiteDrop ?
12881                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12882                   (int) CharToPiece(ToLower(currentMoveString[0]));
12883                 fromY = DROP_RANK;
12884                 toX = currentMoveString[2] - AAA;
12885                 toY = currentMoveString[3] - ONE;
12886                 promoChar = 0;
12887                 break;
12888         }
12889         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12890         plyNr++;
12891         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12892         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12893         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12894         if(appData.findMirror) {
12895             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12896             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12897         }
12898     }
12899 }
12900
12901 /* Load the nth game from open file f */
12902 int
12903 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12904 {
12905     ChessMove cm;
12906     char buf[MSG_SIZ];
12907     int gn = gameNumber;
12908     ListGame *lg = NULL;
12909     int numPGNTags = 0;
12910     int err, pos = -1;
12911     GameMode oldGameMode;
12912     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12913     char oldName[MSG_SIZ];
12914
12915     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12916
12917     if (appData.debugMode)
12918         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12919
12920     if (gameMode == Training )
12921         SetTrainingModeOff();
12922
12923     oldGameMode = gameMode;
12924     if (gameMode != BeginningOfGame) {
12925       Reset(FALSE, TRUE);
12926     }
12927     killX = killY = -1; // [HGM] lion: in case we did not Reset
12928
12929     gameFileFP = f;
12930     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12931         fclose(lastLoadGameFP);
12932     }
12933
12934     if (useList) {
12935         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12936
12937         if (lg) {
12938             fseek(f, lg->offset, 0);
12939             GameListHighlight(gameNumber);
12940             pos = lg->position;
12941             gn = 1;
12942         }
12943         else {
12944             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12945               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12946             else
12947             DisplayError(_("Game number out of range"), 0);
12948             return FALSE;
12949         }
12950     } else {
12951         GameListDestroy();
12952         if (fseek(f, 0, 0) == -1) {
12953             if (f == lastLoadGameFP ?
12954                 gameNumber == lastLoadGameNumber + 1 :
12955                 gameNumber == 1) {
12956                 gn = 1;
12957             } else {
12958                 DisplayError(_("Can't seek on game file"), 0);
12959                 return FALSE;
12960             }
12961         }
12962     }
12963     lastLoadGameFP = f;
12964     lastLoadGameNumber = gameNumber;
12965     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12966     lastLoadGameUseList = useList;
12967
12968     yynewfile(f);
12969
12970     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12971       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12972                 lg->gameInfo.black);
12973             DisplayTitle(buf);
12974     } else if (*title != NULLCHAR) {
12975         if (gameNumber > 1) {
12976           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12977             DisplayTitle(buf);
12978         } else {
12979             DisplayTitle(title);
12980         }
12981     }
12982
12983     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12984         gameMode = PlayFromGameFile;
12985         ModeHighlight();
12986     }
12987
12988     currentMove = forwardMostMove = backwardMostMove = 0;
12989     CopyBoard(boards[0], initialPosition);
12990     StopClocks();
12991
12992     /*
12993      * Skip the first gn-1 games in the file.
12994      * Also skip over anything that precedes an identifiable
12995      * start of game marker, to avoid being confused by
12996      * garbage at the start of the file.  Currently
12997      * recognized start of game markers are the move number "1",
12998      * the pattern "gnuchess .* game", the pattern
12999      * "^[#;%] [^ ]* game file", and a PGN tag block.
13000      * A game that starts with one of the latter two patterns
13001      * will also have a move number 1, possibly
13002      * following a position diagram.
13003      * 5-4-02: Let's try being more lenient and allowing a game to
13004      * start with an unnumbered move.  Does that break anything?
13005      */
13006     cm = lastLoadGameStart = EndOfFile;
13007     while (gn > 0) {
13008         yyboardindex = forwardMostMove;
13009         cm = (ChessMove) Myylex();
13010         switch (cm) {
13011           case EndOfFile:
13012             if (cmailMsgLoaded) {
13013                 nCmailGames = CMAIL_MAX_GAMES - gn;
13014             } else {
13015                 Reset(TRUE, TRUE);
13016                 DisplayError(_("Game not found in file"), 0);
13017             }
13018             return FALSE;
13019
13020           case GNUChessGame:
13021           case XBoardGame:
13022             gn--;
13023             lastLoadGameStart = cm;
13024             break;
13025
13026           case MoveNumberOne:
13027             switch (lastLoadGameStart) {
13028               case GNUChessGame:
13029               case XBoardGame:
13030               case PGNTag:
13031                 break;
13032               case MoveNumberOne:
13033               case EndOfFile:
13034                 gn--;           /* count this game */
13035                 lastLoadGameStart = cm;
13036                 break;
13037               default:
13038                 /* impossible */
13039                 break;
13040             }
13041             break;
13042
13043           case PGNTag:
13044             switch (lastLoadGameStart) {
13045               case GNUChessGame:
13046               case PGNTag:
13047               case MoveNumberOne:
13048               case EndOfFile:
13049                 gn--;           /* count this game */
13050                 lastLoadGameStart = cm;
13051                 break;
13052               case XBoardGame:
13053                 lastLoadGameStart = cm; /* game counted already */
13054                 break;
13055               default:
13056                 /* impossible */
13057                 break;
13058             }
13059             if (gn > 0) {
13060                 do {
13061                     yyboardindex = forwardMostMove;
13062                     cm = (ChessMove) Myylex();
13063                 } while (cm == PGNTag || cm == Comment);
13064             }
13065             break;
13066
13067           case WhiteWins:
13068           case BlackWins:
13069           case GameIsDrawn:
13070             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13071                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13072                     != CMAIL_OLD_RESULT) {
13073                     nCmailResults ++ ;
13074                     cmailResult[  CMAIL_MAX_GAMES
13075                                 - gn - 1] = CMAIL_OLD_RESULT;
13076                 }
13077             }
13078             break;
13079
13080           case NormalMove:
13081           case FirstLeg:
13082             /* Only a NormalMove can be at the start of a game
13083              * without a position diagram. */
13084             if (lastLoadGameStart == EndOfFile ) {
13085               gn--;
13086               lastLoadGameStart = MoveNumberOne;
13087             }
13088             break;
13089
13090           default:
13091             break;
13092         }
13093     }
13094
13095     if (appData.debugMode)
13096       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13097
13098     if (cm == XBoardGame) {
13099         /* Skip any header junk before position diagram and/or move 1 */
13100         for (;;) {
13101             yyboardindex = forwardMostMove;
13102             cm = (ChessMove) Myylex();
13103
13104             if (cm == EndOfFile ||
13105                 cm == GNUChessGame || cm == XBoardGame) {
13106                 /* Empty game; pretend end-of-file and handle later */
13107                 cm = EndOfFile;
13108                 break;
13109             }
13110
13111             if (cm == MoveNumberOne || cm == PositionDiagram ||
13112                 cm == PGNTag || cm == Comment)
13113               break;
13114         }
13115     } else if (cm == GNUChessGame) {
13116         if (gameInfo.event != NULL) {
13117             free(gameInfo.event);
13118         }
13119         gameInfo.event = StrSave(yy_text);
13120     }
13121
13122     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13123     while (cm == PGNTag) {
13124         if (appData.debugMode)
13125           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13126         err = ParsePGNTag(yy_text, &gameInfo);
13127         if (!err) numPGNTags++;
13128
13129         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13130         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13131             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13132             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13133             InitPosition(TRUE);
13134             oldVariant = gameInfo.variant;
13135             if (appData.debugMode)
13136               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13137         }
13138
13139
13140         if (gameInfo.fen != NULL) {
13141           Board initial_position;
13142           startedFromSetupPosition = TRUE;
13143           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13144             Reset(TRUE, TRUE);
13145             DisplayError(_("Bad FEN position in file"), 0);
13146             return FALSE;
13147           }
13148           CopyBoard(boards[0], initial_position);
13149           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13150             CopyBoard(initialPosition, initial_position);
13151           if (blackPlaysFirst) {
13152             currentMove = forwardMostMove = backwardMostMove = 1;
13153             CopyBoard(boards[1], initial_position);
13154             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13155             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13156             timeRemaining[0][1] = whiteTimeRemaining;
13157             timeRemaining[1][1] = blackTimeRemaining;
13158             if (commentList[0] != NULL) {
13159               commentList[1] = commentList[0];
13160               commentList[0] = NULL;
13161             }
13162           } else {
13163             currentMove = forwardMostMove = backwardMostMove = 0;
13164           }
13165           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13166           {   int i;
13167               initialRulePlies = FENrulePlies;
13168               for( i=0; i< nrCastlingRights; i++ )
13169                   initialRights[i] = initial_position[CASTLING][i];
13170           }
13171           yyboardindex = forwardMostMove;
13172           free(gameInfo.fen);
13173           gameInfo.fen = NULL;
13174         }
13175
13176         yyboardindex = forwardMostMove;
13177         cm = (ChessMove) Myylex();
13178
13179         /* Handle comments interspersed among the tags */
13180         while (cm == Comment) {
13181             char *p;
13182             if (appData.debugMode)
13183               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13184             p = yy_text;
13185             AppendComment(currentMove, p, FALSE);
13186             yyboardindex = forwardMostMove;
13187             cm = (ChessMove) Myylex();
13188         }
13189     }
13190
13191     /* don't rely on existence of Event tag since if game was
13192      * pasted from clipboard the Event tag may not exist
13193      */
13194     if (numPGNTags > 0){
13195         char *tags;
13196         if (gameInfo.variant == VariantNormal) {
13197           VariantClass v = StringToVariant(gameInfo.event);
13198           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13199           if(v < VariantShogi) gameInfo.variant = v;
13200         }
13201         if (!matchMode) {
13202           if( appData.autoDisplayTags ) {
13203             tags = PGNTags(&gameInfo);
13204             TagsPopUp(tags, CmailMsg());
13205             free(tags);
13206           }
13207         }
13208     } else {
13209         /* Make something up, but don't display it now */
13210         SetGameInfo();
13211         TagsPopDown();
13212     }
13213
13214     if (cm == PositionDiagram) {
13215         int i, j;
13216         char *p;
13217         Board initial_position;
13218
13219         if (appData.debugMode)
13220           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13221
13222         if (!startedFromSetupPosition) {
13223             p = yy_text;
13224             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13225               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13226                 switch (*p) {
13227                   case '{':
13228                   case '[':
13229                   case '-':
13230                   case ' ':
13231                   case '\t':
13232                   case '\n':
13233                   case '\r':
13234                     break;
13235                   default:
13236                     initial_position[i][j++] = CharToPiece(*p);
13237                     break;
13238                 }
13239             while (*p == ' ' || *p == '\t' ||
13240                    *p == '\n' || *p == '\r') p++;
13241
13242             if (strncmp(p, "black", strlen("black"))==0)
13243               blackPlaysFirst = TRUE;
13244             else
13245               blackPlaysFirst = FALSE;
13246             startedFromSetupPosition = TRUE;
13247
13248             CopyBoard(boards[0], initial_position);
13249             if (blackPlaysFirst) {
13250                 currentMove = forwardMostMove = backwardMostMove = 1;
13251                 CopyBoard(boards[1], initial_position);
13252                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13253                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13254                 timeRemaining[0][1] = whiteTimeRemaining;
13255                 timeRemaining[1][1] = blackTimeRemaining;
13256                 if (commentList[0] != NULL) {
13257                     commentList[1] = commentList[0];
13258                     commentList[0] = NULL;
13259                 }
13260             } else {
13261                 currentMove = forwardMostMove = backwardMostMove = 0;
13262             }
13263         }
13264         yyboardindex = forwardMostMove;
13265         cm = (ChessMove) Myylex();
13266     }
13267
13268   if(!creatingBook) {
13269     if (first.pr == NoProc) {
13270         StartChessProgram(&first);
13271     }
13272     InitChessProgram(&first, FALSE);
13273     if(gameInfo.variant == VariantUnknown && *oldName) {
13274         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13275         gameInfo.variant = v;
13276     }
13277     SendToProgram("force\n", &first);
13278     if (startedFromSetupPosition) {
13279         SendBoard(&first, forwardMostMove);
13280     if (appData.debugMode) {
13281         fprintf(debugFP, "Load Game\n");
13282     }
13283         DisplayBothClocks();
13284     }
13285   }
13286
13287     /* [HGM] server: flag to write setup moves in broadcast file as one */
13288     loadFlag = appData.suppressLoadMoves;
13289
13290     while (cm == Comment) {
13291         char *p;
13292         if (appData.debugMode)
13293           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13294         p = yy_text;
13295         AppendComment(currentMove, p, FALSE);
13296         yyboardindex = forwardMostMove;
13297         cm = (ChessMove) Myylex();
13298     }
13299
13300     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13301         cm == WhiteWins || cm == BlackWins ||
13302         cm == GameIsDrawn || cm == GameUnfinished) {
13303         DisplayMessage("", _("No moves in game"));
13304         if (cmailMsgLoaded) {
13305             if (appData.debugMode)
13306               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13307             ClearHighlights();
13308             flipView = FALSE;
13309         }
13310         DrawPosition(FALSE, boards[currentMove]);
13311         DisplayBothClocks();
13312         gameMode = EditGame;
13313         ModeHighlight();
13314         gameFileFP = NULL;
13315         cmailOldMove = 0;
13316         return TRUE;
13317     }
13318
13319     // [HGM] PV info: routine tests if comment empty
13320     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13321         DisplayComment(currentMove - 1, commentList[currentMove]);
13322     }
13323     if (!matchMode && appData.timeDelay != 0)
13324       DrawPosition(FALSE, boards[currentMove]);
13325
13326     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13327       programStats.ok_to_send = 1;
13328     }
13329
13330     /* if the first token after the PGN tags is a move
13331      * and not move number 1, retrieve it from the parser
13332      */
13333     if (cm != MoveNumberOne)
13334         LoadGameOneMove(cm);
13335
13336     /* load the remaining moves from the file */
13337     while (LoadGameOneMove(EndOfFile)) {
13338       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13339       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13340     }
13341
13342     /* rewind to the start of the game */
13343     currentMove = backwardMostMove;
13344
13345     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13346
13347     if (oldGameMode == AnalyzeFile) {
13348       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13349       AnalyzeFileEvent();
13350     } else
13351     if (oldGameMode == AnalyzeMode) {
13352       AnalyzeFileEvent();
13353     }
13354
13355     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13356         long int w, b; // [HGM] adjourn: restore saved clock times
13357         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13358         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13359             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13360             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13361         }
13362     }
13363
13364     if(creatingBook) return TRUE;
13365     if (!matchMode && pos > 0) {
13366         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13367     } else
13368     if (matchMode || appData.timeDelay == 0) {
13369       ToEndEvent();
13370     } else if (appData.timeDelay > 0) {
13371       AutoPlayGameLoop();
13372     }
13373
13374     if (appData.debugMode)
13375         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13376
13377     loadFlag = 0; /* [HGM] true game starts */
13378     return TRUE;
13379 }
13380
13381 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13382 int
13383 ReloadPosition (int offset)
13384 {
13385     int positionNumber = lastLoadPositionNumber + offset;
13386     if (lastLoadPositionFP == NULL) {
13387         DisplayError(_("No position has been loaded yet"), 0);
13388         return FALSE;
13389     }
13390     if (positionNumber <= 0) {
13391         DisplayError(_("Can't back up any further"), 0);
13392         return FALSE;
13393     }
13394     return LoadPosition(lastLoadPositionFP, positionNumber,
13395                         lastLoadPositionTitle);
13396 }
13397
13398 /* Load the nth position from the given file */
13399 int
13400 LoadPositionFromFile (char *filename, int n, char *title)
13401 {
13402     FILE *f;
13403     char buf[MSG_SIZ];
13404
13405     if (strcmp(filename, "-") == 0) {
13406         return LoadPosition(stdin, n, "stdin");
13407     } else {
13408         f = fopen(filename, "rb");
13409         if (f == NULL) {
13410             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13411             DisplayError(buf, errno);
13412             return FALSE;
13413         } else {
13414             return LoadPosition(f, n, title);
13415         }
13416     }
13417 }
13418
13419 /* Load the nth position from the given open file, and close it */
13420 int
13421 LoadPosition (FILE *f, int positionNumber, char *title)
13422 {
13423     char *p, line[MSG_SIZ];
13424     Board initial_position;
13425     int i, j, fenMode, pn;
13426
13427     if (gameMode == Training )
13428         SetTrainingModeOff();
13429
13430     if (gameMode != BeginningOfGame) {
13431         Reset(FALSE, TRUE);
13432     }
13433     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13434         fclose(lastLoadPositionFP);
13435     }
13436     if (positionNumber == 0) positionNumber = 1;
13437     lastLoadPositionFP = f;
13438     lastLoadPositionNumber = positionNumber;
13439     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13440     if (first.pr == NoProc && !appData.noChessProgram) {
13441       StartChessProgram(&first);
13442       InitChessProgram(&first, FALSE);
13443     }
13444     pn = positionNumber;
13445     if (positionNumber < 0) {
13446         /* Negative position number means to seek to that byte offset */
13447         if (fseek(f, -positionNumber, 0) == -1) {
13448             DisplayError(_("Can't seek on position file"), 0);
13449             return FALSE;
13450         };
13451         pn = 1;
13452     } else {
13453         if (fseek(f, 0, 0) == -1) {
13454             if (f == lastLoadPositionFP ?
13455                 positionNumber == lastLoadPositionNumber + 1 :
13456                 positionNumber == 1) {
13457                 pn = 1;
13458             } else {
13459                 DisplayError(_("Can't seek on position file"), 0);
13460                 return FALSE;
13461             }
13462         }
13463     }
13464     /* See if this file is FEN or old-style xboard */
13465     if (fgets(line, MSG_SIZ, f) == NULL) {
13466         DisplayError(_("Position not found in file"), 0);
13467         return FALSE;
13468     }
13469     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13470     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13471
13472     if (pn >= 2) {
13473         if (fenMode || line[0] == '#') pn--;
13474         while (pn > 0) {
13475             /* skip positions before number pn */
13476             if (fgets(line, MSG_SIZ, f) == NULL) {
13477                 Reset(TRUE, TRUE);
13478                 DisplayError(_("Position not found in file"), 0);
13479                 return FALSE;
13480             }
13481             if (fenMode || line[0] == '#') pn--;
13482         }
13483     }
13484
13485     if (fenMode) {
13486         char *p;
13487         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13488             DisplayError(_("Bad FEN position in file"), 0);
13489             return FALSE;
13490         }
13491         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13492             sscanf(p+3, "%s", bestMove);
13493         } else *bestMove = NULLCHAR;
13494     } else {
13495         (void) fgets(line, MSG_SIZ, f);
13496         (void) fgets(line, MSG_SIZ, f);
13497
13498         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13499             (void) fgets(line, MSG_SIZ, f);
13500             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13501                 if (*p == ' ')
13502                   continue;
13503                 initial_position[i][j++] = CharToPiece(*p);
13504             }
13505         }
13506
13507         blackPlaysFirst = FALSE;
13508         if (!feof(f)) {
13509             (void) fgets(line, MSG_SIZ, f);
13510             if (strncmp(line, "black", strlen("black"))==0)
13511               blackPlaysFirst = TRUE;
13512         }
13513     }
13514     startedFromSetupPosition = TRUE;
13515
13516     CopyBoard(boards[0], initial_position);
13517     if (blackPlaysFirst) {
13518         currentMove = forwardMostMove = backwardMostMove = 1;
13519         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13520         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13521         CopyBoard(boards[1], initial_position);
13522         DisplayMessage("", _("Black to play"));
13523     } else {
13524         currentMove = forwardMostMove = backwardMostMove = 0;
13525         DisplayMessage("", _("White to play"));
13526     }
13527     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13528     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13529         SendToProgram("force\n", &first);
13530         SendBoard(&first, forwardMostMove);
13531     }
13532     if (appData.debugMode) {
13533 int i, j;
13534   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13535   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13536         fprintf(debugFP, "Load Position\n");
13537     }
13538
13539     if (positionNumber > 1) {
13540       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13541         DisplayTitle(line);
13542     } else {
13543         DisplayTitle(title);
13544     }
13545     gameMode = EditGame;
13546     ModeHighlight();
13547     ResetClocks();
13548     timeRemaining[0][1] = whiteTimeRemaining;
13549     timeRemaining[1][1] = blackTimeRemaining;
13550     DrawPosition(FALSE, boards[currentMove]);
13551
13552     return TRUE;
13553 }
13554
13555
13556 void
13557 CopyPlayerNameIntoFileName (char **dest, char *src)
13558 {
13559     while (*src != NULLCHAR && *src != ',') {
13560         if (*src == ' ') {
13561             *(*dest)++ = '_';
13562             src++;
13563         } else {
13564             *(*dest)++ = *src++;
13565         }
13566     }
13567 }
13568
13569 char *
13570 DefaultFileName (char *ext)
13571 {
13572     static char def[MSG_SIZ];
13573     char *p;
13574
13575     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13576         p = def;
13577         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13578         *p++ = '-';
13579         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13580         *p++ = '.';
13581         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13582     } else {
13583         def[0] = NULLCHAR;
13584     }
13585     return def;
13586 }
13587
13588 /* Save the current game to the given file */
13589 int
13590 SaveGameToFile (char *filename, int append)
13591 {
13592     FILE *f;
13593     char buf[MSG_SIZ];
13594     int result, i, t,tot=0;
13595
13596     if (strcmp(filename, "-") == 0) {
13597         return SaveGame(stdout, 0, NULL);
13598     } else {
13599         for(i=0; i<10; i++) { // upto 10 tries
13600              f = fopen(filename, append ? "a" : "w");
13601              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13602              if(f || errno != 13) break;
13603              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13604              tot += t;
13605         }
13606         if (f == NULL) {
13607             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13608             DisplayError(buf, errno);
13609             return FALSE;
13610         } else {
13611             safeStrCpy(buf, lastMsg, MSG_SIZ);
13612             DisplayMessage(_("Waiting for access to save file"), "");
13613             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13614             DisplayMessage(_("Saving game"), "");
13615             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13616             result = SaveGame(f, 0, NULL);
13617             DisplayMessage(buf, "");
13618             return result;
13619         }
13620     }
13621 }
13622
13623 char *
13624 SavePart (char *str)
13625 {
13626     static char buf[MSG_SIZ];
13627     char *p;
13628
13629     p = strchr(str, ' ');
13630     if (p == NULL) return str;
13631     strncpy(buf, str, p - str);
13632     buf[p - str] = NULLCHAR;
13633     return buf;
13634 }
13635
13636 #define PGN_MAX_LINE 75
13637
13638 #define PGN_SIDE_WHITE  0
13639 #define PGN_SIDE_BLACK  1
13640
13641 static int
13642 FindFirstMoveOutOfBook (int side)
13643 {
13644     int result = -1;
13645
13646     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13647         int index = backwardMostMove;
13648         int has_book_hit = 0;
13649
13650         if( (index % 2) != side ) {
13651             index++;
13652         }
13653
13654         while( index < forwardMostMove ) {
13655             /* Check to see if engine is in book */
13656             int depth = pvInfoList[index].depth;
13657             int score = pvInfoList[index].score;
13658             int in_book = 0;
13659
13660             if( depth <= 2 ) {
13661                 in_book = 1;
13662             }
13663             else if( score == 0 && depth == 63 ) {
13664                 in_book = 1; /* Zappa */
13665             }
13666             else if( score == 2 && depth == 99 ) {
13667                 in_book = 1; /* Abrok */
13668             }
13669
13670             has_book_hit += in_book;
13671
13672             if( ! in_book ) {
13673                 result = index;
13674
13675                 break;
13676             }
13677
13678             index += 2;
13679         }
13680     }
13681
13682     return result;
13683 }
13684
13685 void
13686 GetOutOfBookInfo (char * buf)
13687 {
13688     int oob[2];
13689     int i;
13690     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13691
13692     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13693     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13694
13695     *buf = '\0';
13696
13697     if( oob[0] >= 0 || oob[1] >= 0 ) {
13698         for( i=0; i<2; i++ ) {
13699             int idx = oob[i];
13700
13701             if( idx >= 0 ) {
13702                 if( i > 0 && oob[0] >= 0 ) {
13703                     strcat( buf, "   " );
13704                 }
13705
13706                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13707                 sprintf( buf+strlen(buf), "%s%.2f",
13708                     pvInfoList[idx].score >= 0 ? "+" : "",
13709                     pvInfoList[idx].score / 100.0 );
13710             }
13711         }
13712     }
13713 }
13714
13715 /* Save game in PGN style */
13716 static void
13717 SaveGamePGN2 (FILE *f)
13718 {
13719     int i, offset, linelen, newblock;
13720 //    char *movetext;
13721     char numtext[32];
13722     int movelen, numlen, blank;
13723     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13724
13725     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13726
13727     PrintPGNTags(f, &gameInfo);
13728
13729     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13730
13731     if (backwardMostMove > 0 || startedFromSetupPosition) {
13732         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13733         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13734         fprintf(f, "\n{--------------\n");
13735         PrintPosition(f, backwardMostMove);
13736         fprintf(f, "--------------}\n");
13737         free(fen);
13738     }
13739     else {
13740         /* [AS] Out of book annotation */
13741         if( appData.saveOutOfBookInfo ) {
13742             char buf[64];
13743
13744             GetOutOfBookInfo( buf );
13745
13746             if( buf[0] != '\0' ) {
13747                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13748             }
13749         }
13750
13751         fprintf(f, "\n");
13752     }
13753
13754     i = backwardMostMove;
13755     linelen = 0;
13756     newblock = TRUE;
13757
13758     while (i < forwardMostMove) {
13759         /* Print comments preceding this move */
13760         if (commentList[i] != NULL) {
13761             if (linelen > 0) fprintf(f, "\n");
13762             fprintf(f, "%s", commentList[i]);
13763             linelen = 0;
13764             newblock = TRUE;
13765         }
13766
13767         /* Format move number */
13768         if ((i % 2) == 0)
13769           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13770         else
13771           if (newblock)
13772             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13773           else
13774             numtext[0] = NULLCHAR;
13775
13776         numlen = strlen(numtext);
13777         newblock = FALSE;
13778
13779         /* Print move number */
13780         blank = linelen > 0 && numlen > 0;
13781         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13782             fprintf(f, "\n");
13783             linelen = 0;
13784             blank = 0;
13785         }
13786         if (blank) {
13787             fprintf(f, " ");
13788             linelen++;
13789         }
13790         fprintf(f, "%s", numtext);
13791         linelen += numlen;
13792
13793         /* Get move */
13794         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13795         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13796
13797         /* Print move */
13798         blank = linelen > 0 && movelen > 0;
13799         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13800             fprintf(f, "\n");
13801             linelen = 0;
13802             blank = 0;
13803         }
13804         if (blank) {
13805             fprintf(f, " ");
13806             linelen++;
13807         }
13808         fprintf(f, "%s", move_buffer);
13809         linelen += movelen;
13810
13811         /* [AS] Add PV info if present */
13812         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13813             /* [HGM] add time */
13814             char buf[MSG_SIZ]; int seconds;
13815
13816             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13817
13818             if( seconds <= 0)
13819               buf[0] = 0;
13820             else
13821               if( seconds < 30 )
13822                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13823               else
13824                 {
13825                   seconds = (seconds + 4)/10; // round to full seconds
13826                   if( seconds < 60 )
13827                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13828                   else
13829                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13830                 }
13831
13832             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13833                       pvInfoList[i].score >= 0 ? "+" : "",
13834                       pvInfoList[i].score / 100.0,
13835                       pvInfoList[i].depth,
13836                       buf );
13837
13838             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13839
13840             /* Print score/depth */
13841             blank = linelen > 0 && movelen > 0;
13842             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13843                 fprintf(f, "\n");
13844                 linelen = 0;
13845                 blank = 0;
13846             }
13847             if (blank) {
13848                 fprintf(f, " ");
13849                 linelen++;
13850             }
13851             fprintf(f, "%s", move_buffer);
13852             linelen += movelen;
13853         }
13854
13855         i++;
13856     }
13857
13858     /* Start a new line */
13859     if (linelen > 0) fprintf(f, "\n");
13860
13861     /* Print comments after last move */
13862     if (commentList[i] != NULL) {
13863         fprintf(f, "%s\n", commentList[i]);
13864     }
13865
13866     /* Print result */
13867     if (gameInfo.resultDetails != NULL &&
13868         gameInfo.resultDetails[0] != NULLCHAR) {
13869         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13870         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13871            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13872             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13873         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13874     } else {
13875         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13876     }
13877 }
13878
13879 /* Save game in PGN style and close the file */
13880 int
13881 SaveGamePGN (FILE *f)
13882 {
13883     SaveGamePGN2(f);
13884     fclose(f);
13885     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13886     return TRUE;
13887 }
13888
13889 /* Save game in old style and close the file */
13890 int
13891 SaveGameOldStyle (FILE *f)
13892 {
13893     int i, offset;
13894     time_t tm;
13895
13896     tm = time((time_t *) NULL);
13897
13898     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13899     PrintOpponents(f);
13900
13901     if (backwardMostMove > 0 || startedFromSetupPosition) {
13902         fprintf(f, "\n[--------------\n");
13903         PrintPosition(f, backwardMostMove);
13904         fprintf(f, "--------------]\n");
13905     } else {
13906         fprintf(f, "\n");
13907     }
13908
13909     i = backwardMostMove;
13910     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13911
13912     while (i < forwardMostMove) {
13913         if (commentList[i] != NULL) {
13914             fprintf(f, "[%s]\n", commentList[i]);
13915         }
13916
13917         if ((i % 2) == 1) {
13918             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13919             i++;
13920         } else {
13921             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13922             i++;
13923             if (commentList[i] != NULL) {
13924                 fprintf(f, "\n");
13925                 continue;
13926             }
13927             if (i >= forwardMostMove) {
13928                 fprintf(f, "\n");
13929                 break;
13930             }
13931             fprintf(f, "%s\n", parseList[i]);
13932             i++;
13933         }
13934     }
13935
13936     if (commentList[i] != NULL) {
13937         fprintf(f, "[%s]\n", commentList[i]);
13938     }
13939
13940     /* This isn't really the old style, but it's close enough */
13941     if (gameInfo.resultDetails != NULL &&
13942         gameInfo.resultDetails[0] != NULLCHAR) {
13943         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13944                 gameInfo.resultDetails);
13945     } else {
13946         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13947     }
13948
13949     fclose(f);
13950     return TRUE;
13951 }
13952
13953 /* Save the current game to open file f and close the file */
13954 int
13955 SaveGame (FILE *f, int dummy, char *dummy2)
13956 {
13957     if (gameMode == EditPosition) EditPositionDone(TRUE);
13958     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13959     if (appData.oldSaveStyle)
13960       return SaveGameOldStyle(f);
13961     else
13962       return SaveGamePGN(f);
13963 }
13964
13965 /* Save the current position to the given file */
13966 int
13967 SavePositionToFile (char *filename)
13968 {
13969     FILE *f;
13970     char buf[MSG_SIZ];
13971
13972     if (strcmp(filename, "-") == 0) {
13973         return SavePosition(stdout, 0, NULL);
13974     } else {
13975         f = fopen(filename, "a");
13976         if (f == NULL) {
13977             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13978             DisplayError(buf, errno);
13979             return FALSE;
13980         } else {
13981             safeStrCpy(buf, lastMsg, MSG_SIZ);
13982             DisplayMessage(_("Waiting for access to save file"), "");
13983             flock(fileno(f), LOCK_EX); // [HGM] lock
13984             DisplayMessage(_("Saving position"), "");
13985             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13986             SavePosition(f, 0, NULL);
13987             DisplayMessage(buf, "");
13988             return TRUE;
13989         }
13990     }
13991 }
13992
13993 /* Save the current position to the given open file and close the file */
13994 int
13995 SavePosition (FILE *f, int dummy, char *dummy2)
13996 {
13997     time_t tm;
13998     char *fen;
13999
14000     if (gameMode == EditPosition) EditPositionDone(TRUE);
14001     if (appData.oldSaveStyle) {
14002         tm = time((time_t *) NULL);
14003
14004         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14005         PrintOpponents(f);
14006         fprintf(f, "[--------------\n");
14007         PrintPosition(f, currentMove);
14008         fprintf(f, "--------------]\n");
14009     } else {
14010         fen = PositionToFEN(currentMove, NULL, 1);
14011         fprintf(f, "%s\n", fen);
14012         free(fen);
14013     }
14014     fclose(f);
14015     return TRUE;
14016 }
14017
14018 void
14019 ReloadCmailMsgEvent (int unregister)
14020 {
14021 #if !WIN32
14022     static char *inFilename = NULL;
14023     static char *outFilename;
14024     int i;
14025     struct stat inbuf, outbuf;
14026     int status;
14027
14028     /* Any registered moves are unregistered if unregister is set, */
14029     /* i.e. invoked by the signal handler */
14030     if (unregister) {
14031         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14032             cmailMoveRegistered[i] = FALSE;
14033             if (cmailCommentList[i] != NULL) {
14034                 free(cmailCommentList[i]);
14035                 cmailCommentList[i] = NULL;
14036             }
14037         }
14038         nCmailMovesRegistered = 0;
14039     }
14040
14041     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14042         cmailResult[i] = CMAIL_NOT_RESULT;
14043     }
14044     nCmailResults = 0;
14045
14046     if (inFilename == NULL) {
14047         /* Because the filenames are static they only get malloced once  */
14048         /* and they never get freed                                      */
14049         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14050         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14051
14052         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14053         sprintf(outFilename, "%s.out", appData.cmailGameName);
14054     }
14055
14056     status = stat(outFilename, &outbuf);
14057     if (status < 0) {
14058         cmailMailedMove = FALSE;
14059     } else {
14060         status = stat(inFilename, &inbuf);
14061         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14062     }
14063
14064     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14065        counts the games, notes how each one terminated, etc.
14066
14067        It would be nice to remove this kludge and instead gather all
14068        the information while building the game list.  (And to keep it
14069        in the game list nodes instead of having a bunch of fixed-size
14070        parallel arrays.)  Note this will require getting each game's
14071        termination from the PGN tags, as the game list builder does
14072        not process the game moves.  --mann
14073        */
14074     cmailMsgLoaded = TRUE;
14075     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14076
14077     /* Load first game in the file or popup game menu */
14078     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14079
14080 #endif /* !WIN32 */
14081     return;
14082 }
14083
14084 int
14085 RegisterMove ()
14086 {
14087     FILE *f;
14088     char string[MSG_SIZ];
14089
14090     if (   cmailMailedMove
14091         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14092         return TRUE;            /* Allow free viewing  */
14093     }
14094
14095     /* Unregister move to ensure that we don't leave RegisterMove        */
14096     /* with the move registered when the conditions for registering no   */
14097     /* longer hold                                                       */
14098     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14099         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14100         nCmailMovesRegistered --;
14101
14102         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14103           {
14104               free(cmailCommentList[lastLoadGameNumber - 1]);
14105               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14106           }
14107     }
14108
14109     if (cmailOldMove == -1) {
14110         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14111         return FALSE;
14112     }
14113
14114     if (currentMove > cmailOldMove + 1) {
14115         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14116         return FALSE;
14117     }
14118
14119     if (currentMove < cmailOldMove) {
14120         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14121         return FALSE;
14122     }
14123
14124     if (forwardMostMove > currentMove) {
14125         /* Silently truncate extra moves */
14126         TruncateGame();
14127     }
14128
14129     if (   (currentMove == cmailOldMove + 1)
14130         || (   (currentMove == cmailOldMove)
14131             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14132                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14133         if (gameInfo.result != GameUnfinished) {
14134             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14135         }
14136
14137         if (commentList[currentMove] != NULL) {
14138             cmailCommentList[lastLoadGameNumber - 1]
14139               = StrSave(commentList[currentMove]);
14140         }
14141         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14142
14143         if (appData.debugMode)
14144           fprintf(debugFP, "Saving %s for game %d\n",
14145                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14146
14147         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14148
14149         f = fopen(string, "w");
14150         if (appData.oldSaveStyle) {
14151             SaveGameOldStyle(f); /* also closes the file */
14152
14153             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14154             f = fopen(string, "w");
14155             SavePosition(f, 0, NULL); /* also closes the file */
14156         } else {
14157             fprintf(f, "{--------------\n");
14158             PrintPosition(f, currentMove);
14159             fprintf(f, "--------------}\n\n");
14160
14161             SaveGame(f, 0, NULL); /* also closes the file*/
14162         }
14163
14164         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14165         nCmailMovesRegistered ++;
14166     } else if (nCmailGames == 1) {
14167         DisplayError(_("You have not made a move yet"), 0);
14168         return FALSE;
14169     }
14170
14171     return TRUE;
14172 }
14173
14174 void
14175 MailMoveEvent ()
14176 {
14177 #if !WIN32
14178     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14179     FILE *commandOutput;
14180     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14181     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14182     int nBuffers;
14183     int i;
14184     int archived;
14185     char *arcDir;
14186
14187     if (! cmailMsgLoaded) {
14188         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14189         return;
14190     }
14191
14192     if (nCmailGames == nCmailResults) {
14193         DisplayError(_("No unfinished games"), 0);
14194         return;
14195     }
14196
14197 #if CMAIL_PROHIBIT_REMAIL
14198     if (cmailMailedMove) {
14199       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);
14200         DisplayError(msg, 0);
14201         return;
14202     }
14203 #endif
14204
14205     if (! (cmailMailedMove || RegisterMove())) return;
14206
14207     if (   cmailMailedMove
14208         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14209       snprintf(string, MSG_SIZ, partCommandString,
14210                appData.debugMode ? " -v" : "", appData.cmailGameName);
14211         commandOutput = popen(string, "r");
14212
14213         if (commandOutput == NULL) {
14214             DisplayError(_("Failed to invoke cmail"), 0);
14215         } else {
14216             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14217                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14218             }
14219             if (nBuffers > 1) {
14220                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14221                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14222                 nBytes = MSG_SIZ - 1;
14223             } else {
14224                 (void) memcpy(msg, buffer, nBytes);
14225             }
14226             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14227
14228             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14229                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14230
14231                 archived = TRUE;
14232                 for (i = 0; i < nCmailGames; i ++) {
14233                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14234                         archived = FALSE;
14235                     }
14236                 }
14237                 if (   archived
14238                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14239                         != NULL)) {
14240                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14241                            arcDir,
14242                            appData.cmailGameName,
14243                            gameInfo.date);
14244                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14245                     cmailMsgLoaded = FALSE;
14246                 }
14247             }
14248
14249             DisplayInformation(msg);
14250             pclose(commandOutput);
14251         }
14252     } else {
14253         if ((*cmailMsg) != '\0') {
14254             DisplayInformation(cmailMsg);
14255         }
14256     }
14257
14258     return;
14259 #endif /* !WIN32 */
14260 }
14261
14262 char *
14263 CmailMsg ()
14264 {
14265 #if WIN32
14266     return NULL;
14267 #else
14268     int  prependComma = 0;
14269     char number[5];
14270     char string[MSG_SIZ];       /* Space for game-list */
14271     int  i;
14272
14273     if (!cmailMsgLoaded) return "";
14274
14275     if (cmailMailedMove) {
14276       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14277     } else {
14278         /* Create a list of games left */
14279       snprintf(string, MSG_SIZ, "[");
14280         for (i = 0; i < nCmailGames; i ++) {
14281             if (! (   cmailMoveRegistered[i]
14282                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14283                 if (prependComma) {
14284                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14285                 } else {
14286                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14287                     prependComma = 1;
14288                 }
14289
14290                 strcat(string, number);
14291             }
14292         }
14293         strcat(string, "]");
14294
14295         if (nCmailMovesRegistered + nCmailResults == 0) {
14296             switch (nCmailGames) {
14297               case 1:
14298                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14299                 break;
14300
14301               case 2:
14302                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14303                 break;
14304
14305               default:
14306                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14307                          nCmailGames);
14308                 break;
14309             }
14310         } else {
14311             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14312               case 1:
14313                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14314                          string);
14315                 break;
14316
14317               case 0:
14318                 if (nCmailResults == nCmailGames) {
14319                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14320                 } else {
14321                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14322                 }
14323                 break;
14324
14325               default:
14326                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14327                          string);
14328             }
14329         }
14330     }
14331     return cmailMsg;
14332 #endif /* WIN32 */
14333 }
14334
14335 void
14336 ResetGameEvent ()
14337 {
14338     if (gameMode == Training)
14339       SetTrainingModeOff();
14340
14341     Reset(TRUE, TRUE);
14342     cmailMsgLoaded = FALSE;
14343     if (appData.icsActive) {
14344       SendToICS(ics_prefix);
14345       SendToICS("refresh\n");
14346     }
14347 }
14348
14349 void
14350 ExitEvent (int status)
14351 {
14352     exiting++;
14353     if (exiting > 2) {
14354       /* Give up on clean exit */
14355       exit(status);
14356     }
14357     if (exiting > 1) {
14358       /* Keep trying for clean exit */
14359       return;
14360     }
14361
14362     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14363     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14364
14365     if (telnetISR != NULL) {
14366       RemoveInputSource(telnetISR);
14367     }
14368     if (icsPR != NoProc) {
14369       DestroyChildProcess(icsPR, TRUE);
14370     }
14371
14372     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14373     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14374
14375     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14376     /* make sure this other one finishes before killing it!                  */
14377     if(endingGame) { int count = 0;
14378         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14379         while(endingGame && count++ < 10) DoSleep(1);
14380         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14381     }
14382
14383     /* Kill off chess programs */
14384     if (first.pr != NoProc) {
14385         ExitAnalyzeMode();
14386
14387         DoSleep( appData.delayBeforeQuit );
14388         SendToProgram("quit\n", &first);
14389         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14390     }
14391     if (second.pr != NoProc) {
14392         DoSleep( appData.delayBeforeQuit );
14393         SendToProgram("quit\n", &second);
14394         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14395     }
14396     if (first.isr != NULL) {
14397         RemoveInputSource(first.isr);
14398     }
14399     if (second.isr != NULL) {
14400         RemoveInputSource(second.isr);
14401     }
14402
14403     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14404     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14405
14406     ShutDownFrontEnd();
14407     exit(status);
14408 }
14409
14410 void
14411 PauseEngine (ChessProgramState *cps)
14412 {
14413     SendToProgram("pause\n", cps);
14414     cps->pause = 2;
14415 }
14416
14417 void
14418 UnPauseEngine (ChessProgramState *cps)
14419 {
14420     SendToProgram("resume\n", cps);
14421     cps->pause = 1;
14422 }
14423
14424 void
14425 PauseEvent ()
14426 {
14427     if (appData.debugMode)
14428         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14429     if (pausing) {
14430         pausing = FALSE;
14431         ModeHighlight();
14432         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14433             StartClocks();
14434             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14435                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14436                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14437             }
14438             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14439             HandleMachineMove(stashedInputMove, stalledEngine);
14440             stalledEngine = NULL;
14441             return;
14442         }
14443         if (gameMode == MachinePlaysWhite ||
14444             gameMode == TwoMachinesPlay   ||
14445             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14446             if(first.pause)  UnPauseEngine(&first);
14447             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14448             if(second.pause) UnPauseEngine(&second);
14449             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14450             StartClocks();
14451         } else {
14452             DisplayBothClocks();
14453         }
14454         if (gameMode == PlayFromGameFile) {
14455             if (appData.timeDelay >= 0)
14456                 AutoPlayGameLoop();
14457         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14458             Reset(FALSE, TRUE);
14459             SendToICS(ics_prefix);
14460             SendToICS("refresh\n");
14461         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14462             ForwardInner(forwardMostMove);
14463         }
14464         pauseExamInvalid = FALSE;
14465     } else {
14466         switch (gameMode) {
14467           default:
14468             return;
14469           case IcsExamining:
14470             pauseExamForwardMostMove = forwardMostMove;
14471             pauseExamInvalid = FALSE;
14472             /* fall through */
14473           case IcsObserving:
14474           case IcsPlayingWhite:
14475           case IcsPlayingBlack:
14476             pausing = TRUE;
14477             ModeHighlight();
14478             return;
14479           case PlayFromGameFile:
14480             (void) StopLoadGameTimer();
14481             pausing = TRUE;
14482             ModeHighlight();
14483             break;
14484           case BeginningOfGame:
14485             if (appData.icsActive) return;
14486             /* else fall through */
14487           case MachinePlaysWhite:
14488           case MachinePlaysBlack:
14489           case TwoMachinesPlay:
14490             if (forwardMostMove == 0)
14491               return;           /* don't pause if no one has moved */
14492             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14493                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14494                 if(onMove->pause) {           // thinking engine can be paused
14495                     PauseEngine(onMove);      // do it
14496                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14497                         PauseEngine(onMove->other);
14498                     else
14499                         SendToProgram("easy\n", onMove->other);
14500                     StopClocks();
14501                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14502             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14503                 if(first.pause) {
14504                     PauseEngine(&first);
14505                     StopClocks();
14506                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14507             } else { // human on move, pause pondering by either method
14508                 if(first.pause)
14509                     PauseEngine(&first);
14510                 else if(appData.ponderNextMove)
14511                     SendToProgram("easy\n", &first);
14512                 StopClocks();
14513             }
14514             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14515           case AnalyzeMode:
14516             pausing = TRUE;
14517             ModeHighlight();
14518             break;
14519         }
14520     }
14521 }
14522
14523 void
14524 EditCommentEvent ()
14525 {
14526     char title[MSG_SIZ];
14527
14528     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14529       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14530     } else {
14531       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14532                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14533                parseList[currentMove - 1]);
14534     }
14535
14536     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14537 }
14538
14539
14540 void
14541 EditTagsEvent ()
14542 {
14543     char *tags = PGNTags(&gameInfo);
14544     bookUp = FALSE;
14545     EditTagsPopUp(tags, NULL);
14546     free(tags);
14547 }
14548
14549 void
14550 ToggleSecond ()
14551 {
14552   if(second.analyzing) {
14553     SendToProgram("exit\n", &second);
14554     second.analyzing = FALSE;
14555   } else {
14556     if (second.pr == NoProc) StartChessProgram(&second);
14557     InitChessProgram(&second, FALSE);
14558     FeedMovesToProgram(&second, currentMove);
14559
14560     SendToProgram("analyze\n", &second);
14561     second.analyzing = TRUE;
14562   }
14563 }
14564
14565 /* Toggle ShowThinking */
14566 void
14567 ToggleShowThinking()
14568 {
14569   appData.showThinking = !appData.showThinking;
14570   ShowThinkingEvent();
14571 }
14572
14573 int
14574 AnalyzeModeEvent ()
14575 {
14576     char buf[MSG_SIZ];
14577
14578     if (!first.analysisSupport) {
14579       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14580       DisplayError(buf, 0);
14581       return 0;
14582     }
14583     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14584     if (appData.icsActive) {
14585         if (gameMode != IcsObserving) {
14586           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14587             DisplayError(buf, 0);
14588             /* secure check */
14589             if (appData.icsEngineAnalyze) {
14590                 if (appData.debugMode)
14591                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14592                 ExitAnalyzeMode();
14593                 ModeHighlight();
14594             }
14595             return 0;
14596         }
14597         /* if enable, user wants to disable icsEngineAnalyze */
14598         if (appData.icsEngineAnalyze) {
14599                 ExitAnalyzeMode();
14600                 ModeHighlight();
14601                 return 0;
14602         }
14603         appData.icsEngineAnalyze = TRUE;
14604         if (appData.debugMode)
14605             fprintf(debugFP, "ICS engine analyze starting... \n");
14606     }
14607
14608     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14609     if (appData.noChessProgram || gameMode == AnalyzeMode)
14610       return 0;
14611
14612     if (gameMode != AnalyzeFile) {
14613         if (!appData.icsEngineAnalyze) {
14614                EditGameEvent();
14615                if (gameMode != EditGame) return 0;
14616         }
14617         if (!appData.showThinking) ToggleShowThinking();
14618         ResurrectChessProgram();
14619         SendToProgram("analyze\n", &first);
14620         first.analyzing = TRUE;
14621         /*first.maybeThinking = TRUE;*/
14622         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14623         EngineOutputPopUp();
14624     }
14625     if (!appData.icsEngineAnalyze) {
14626         gameMode = AnalyzeMode;
14627         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14628     }
14629     pausing = FALSE;
14630     ModeHighlight();
14631     SetGameInfo();
14632
14633     StartAnalysisClock();
14634     GetTimeMark(&lastNodeCountTime);
14635     lastNodeCount = 0;
14636     return 1;
14637 }
14638
14639 void
14640 AnalyzeFileEvent ()
14641 {
14642     if (appData.noChessProgram || gameMode == AnalyzeFile)
14643       return;
14644
14645     if (!first.analysisSupport) {
14646       char buf[MSG_SIZ];
14647       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14648       DisplayError(buf, 0);
14649       return;
14650     }
14651
14652     if (gameMode != AnalyzeMode) {
14653         keepInfo = 1; // mere annotating should not alter PGN tags
14654         EditGameEvent();
14655         keepInfo = 0;
14656         if (gameMode != EditGame) return;
14657         if (!appData.showThinking) ToggleShowThinking();
14658         ResurrectChessProgram();
14659         SendToProgram("analyze\n", &first);
14660         first.analyzing = TRUE;
14661         /*first.maybeThinking = TRUE;*/
14662         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14663         EngineOutputPopUp();
14664     }
14665     gameMode = AnalyzeFile;
14666     pausing = FALSE;
14667     ModeHighlight();
14668
14669     StartAnalysisClock();
14670     GetTimeMark(&lastNodeCountTime);
14671     lastNodeCount = 0;
14672     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14673     AnalysisPeriodicEvent(1);
14674 }
14675
14676 void
14677 MachineWhiteEvent ()
14678 {
14679     char buf[MSG_SIZ];
14680     char *bookHit = NULL;
14681
14682     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14683       return;
14684
14685
14686     if (gameMode == PlayFromGameFile ||
14687         gameMode == TwoMachinesPlay  ||
14688         gameMode == Training         ||
14689         gameMode == AnalyzeMode      ||
14690         gameMode == EndOfGame)
14691         EditGameEvent();
14692
14693     if (gameMode == EditPosition)
14694         EditPositionDone(TRUE);
14695
14696     if (!WhiteOnMove(currentMove)) {
14697         DisplayError(_("It is not White's turn"), 0);
14698         return;
14699     }
14700
14701     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14702       ExitAnalyzeMode();
14703
14704     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14705         gameMode == AnalyzeFile)
14706         TruncateGame();
14707
14708     ResurrectChessProgram();    /* in case it isn't running */
14709     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14710         gameMode = MachinePlaysWhite;
14711         ResetClocks();
14712     } else
14713     gameMode = MachinePlaysWhite;
14714     pausing = FALSE;
14715     ModeHighlight();
14716     SetGameInfo();
14717     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14718     DisplayTitle(buf);
14719     if (first.sendName) {
14720       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14721       SendToProgram(buf, &first);
14722     }
14723     if (first.sendTime) {
14724       if (first.useColors) {
14725         SendToProgram("black\n", &first); /*gnu kludge*/
14726       }
14727       SendTimeRemaining(&first, TRUE);
14728     }
14729     if (first.useColors) {
14730       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14731     }
14732     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14733     SetMachineThinkingEnables();
14734     first.maybeThinking = TRUE;
14735     StartClocks();
14736     firstMove = FALSE;
14737
14738     if (appData.autoFlipView && !flipView) {
14739       flipView = !flipView;
14740       DrawPosition(FALSE, NULL);
14741       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14742     }
14743
14744     if(bookHit) { // [HGM] book: simulate book reply
14745         static char bookMove[MSG_SIZ]; // a bit generous?
14746
14747         programStats.nodes = programStats.depth = programStats.time =
14748         programStats.score = programStats.got_only_move = 0;
14749         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14750
14751         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14752         strcat(bookMove, bookHit);
14753         HandleMachineMove(bookMove, &first);
14754     }
14755 }
14756
14757 void
14758 MachineBlackEvent ()
14759 {
14760   char buf[MSG_SIZ];
14761   char *bookHit = NULL;
14762
14763     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14764         return;
14765
14766
14767     if (gameMode == PlayFromGameFile ||
14768         gameMode == TwoMachinesPlay  ||
14769         gameMode == Training         ||
14770         gameMode == AnalyzeMode      ||
14771         gameMode == EndOfGame)
14772         EditGameEvent();
14773
14774     if (gameMode == EditPosition)
14775         EditPositionDone(TRUE);
14776
14777     if (WhiteOnMove(currentMove)) {
14778         DisplayError(_("It is not Black's turn"), 0);
14779         return;
14780     }
14781
14782     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14783       ExitAnalyzeMode();
14784
14785     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14786         gameMode == AnalyzeFile)
14787         TruncateGame();
14788
14789     ResurrectChessProgram();    /* in case it isn't running */
14790     gameMode = MachinePlaysBlack;
14791     pausing = FALSE;
14792     ModeHighlight();
14793     SetGameInfo();
14794     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14795     DisplayTitle(buf);
14796     if (first.sendName) {
14797       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14798       SendToProgram(buf, &first);
14799     }
14800     if (first.sendTime) {
14801       if (first.useColors) {
14802         SendToProgram("white\n", &first); /*gnu kludge*/
14803       }
14804       SendTimeRemaining(&first, FALSE);
14805     }
14806     if (first.useColors) {
14807       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14808     }
14809     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14810     SetMachineThinkingEnables();
14811     first.maybeThinking = TRUE;
14812     StartClocks();
14813
14814     if (appData.autoFlipView && flipView) {
14815       flipView = !flipView;
14816       DrawPosition(FALSE, NULL);
14817       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14818     }
14819     if(bookHit) { // [HGM] book: simulate book reply
14820         static char bookMove[MSG_SIZ]; // a bit generous?
14821
14822         programStats.nodes = programStats.depth = programStats.time =
14823         programStats.score = programStats.got_only_move = 0;
14824         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14825
14826         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14827         strcat(bookMove, bookHit);
14828         HandleMachineMove(bookMove, &first);
14829     }
14830 }
14831
14832
14833 void
14834 DisplayTwoMachinesTitle ()
14835 {
14836     char buf[MSG_SIZ];
14837     if (appData.matchGames > 0) {
14838         if(appData.tourneyFile[0]) {
14839           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14840                    gameInfo.white, _("vs."), gameInfo.black,
14841                    nextGame+1, appData.matchGames+1,
14842                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14843         } else
14844         if (first.twoMachinesColor[0] == 'w') {
14845           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14846                    gameInfo.white, _("vs."),  gameInfo.black,
14847                    first.matchWins, second.matchWins,
14848                    matchGame - 1 - (first.matchWins + second.matchWins));
14849         } else {
14850           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14851                    gameInfo.white, _("vs."), gameInfo.black,
14852                    second.matchWins, first.matchWins,
14853                    matchGame - 1 - (first.matchWins + second.matchWins));
14854         }
14855     } else {
14856       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14857     }
14858     DisplayTitle(buf);
14859 }
14860
14861 void
14862 SettingsMenuIfReady ()
14863 {
14864   if (second.lastPing != second.lastPong) {
14865     DisplayMessage("", _("Waiting for second chess program"));
14866     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14867     return;
14868   }
14869   ThawUI();
14870   DisplayMessage("", "");
14871   SettingsPopUp(&second);
14872 }
14873
14874 int
14875 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14876 {
14877     char buf[MSG_SIZ];
14878     if (cps->pr == NoProc) {
14879         StartChessProgram(cps);
14880         if (cps->protocolVersion == 1) {
14881           retry();
14882           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14883         } else {
14884           /* kludge: allow timeout for initial "feature" command */
14885           if(retry != TwoMachinesEventIfReady) FreezeUI();
14886           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14887           DisplayMessage("", buf);
14888           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14889         }
14890         return 1;
14891     }
14892     return 0;
14893 }
14894
14895 void
14896 TwoMachinesEvent P((void))
14897 {
14898     int i;
14899     char buf[MSG_SIZ];
14900     ChessProgramState *onmove;
14901     char *bookHit = NULL;
14902     static int stalling = 0;
14903     TimeMark now;
14904     long wait;
14905
14906     if (appData.noChessProgram) return;
14907
14908     switch (gameMode) {
14909       case TwoMachinesPlay:
14910         return;
14911       case MachinePlaysWhite:
14912       case MachinePlaysBlack:
14913         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14914             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14915             return;
14916         }
14917         /* fall through */
14918       case BeginningOfGame:
14919       case PlayFromGameFile:
14920       case EndOfGame:
14921         EditGameEvent();
14922         if (gameMode != EditGame) return;
14923         break;
14924       case EditPosition:
14925         EditPositionDone(TRUE);
14926         break;
14927       case AnalyzeMode:
14928       case AnalyzeFile:
14929         ExitAnalyzeMode();
14930         break;
14931       case EditGame:
14932       default:
14933         break;
14934     }
14935
14936 //    forwardMostMove = currentMove;
14937     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14938     startingEngine = TRUE;
14939
14940     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14941
14942     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14943     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14944       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14945       return;
14946     }
14947     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14948
14949     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14950                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14951         startingEngine = matchMode = FALSE;
14952         DisplayError("second engine does not play this", 0);
14953         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14954         EditGameEvent(); // switch back to EditGame mode
14955         return;
14956     }
14957
14958     if(!stalling) {
14959       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14960       SendToProgram("force\n", &second);
14961       stalling = 1;
14962       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14963       return;
14964     }
14965     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14966     if(appData.matchPause>10000 || appData.matchPause<10)
14967                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14968     wait = SubtractTimeMarks(&now, &pauseStart);
14969     if(wait < appData.matchPause) {
14970         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14971         return;
14972     }
14973     // we are now committed to starting the game
14974     stalling = 0;
14975     DisplayMessage("", "");
14976     if (startedFromSetupPosition) {
14977         SendBoard(&second, backwardMostMove);
14978     if (appData.debugMode) {
14979         fprintf(debugFP, "Two Machines\n");
14980     }
14981     }
14982     for (i = backwardMostMove; i < forwardMostMove; i++) {
14983         SendMoveToProgram(i, &second);
14984     }
14985
14986     gameMode = TwoMachinesPlay;
14987     pausing = startingEngine = FALSE;
14988     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14989     SetGameInfo();
14990     DisplayTwoMachinesTitle();
14991     firstMove = TRUE;
14992     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14993         onmove = &first;
14994     } else {
14995         onmove = &second;
14996     }
14997     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14998     SendToProgram(first.computerString, &first);
14999     if (first.sendName) {
15000       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15001       SendToProgram(buf, &first);
15002     }
15003     SendToProgram(second.computerString, &second);
15004     if (second.sendName) {
15005       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15006       SendToProgram(buf, &second);
15007     }
15008
15009     ResetClocks();
15010     if (!first.sendTime || !second.sendTime) {
15011         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15012         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15013     }
15014     if (onmove->sendTime) {
15015       if (onmove->useColors) {
15016         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15017       }
15018       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15019     }
15020     if (onmove->useColors) {
15021       SendToProgram(onmove->twoMachinesColor, onmove);
15022     }
15023     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15024 //    SendToProgram("go\n", onmove);
15025     onmove->maybeThinking = TRUE;
15026     SetMachineThinkingEnables();
15027
15028     StartClocks();
15029
15030     if(bookHit) { // [HGM] book: simulate book reply
15031         static char bookMove[MSG_SIZ]; // a bit generous?
15032
15033         programStats.nodes = programStats.depth = programStats.time =
15034         programStats.score = programStats.got_only_move = 0;
15035         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15036
15037         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15038         strcat(bookMove, bookHit);
15039         savedMessage = bookMove; // args for deferred call
15040         savedState = onmove;
15041         ScheduleDelayedEvent(DeferredBookMove, 1);
15042     }
15043 }
15044
15045 void
15046 TrainingEvent ()
15047 {
15048     if (gameMode == Training) {
15049       SetTrainingModeOff();
15050       gameMode = PlayFromGameFile;
15051       DisplayMessage("", _("Training mode off"));
15052     } else {
15053       gameMode = Training;
15054       animateTraining = appData.animate;
15055
15056       /* make sure we are not already at the end of the game */
15057       if (currentMove < forwardMostMove) {
15058         SetTrainingModeOn();
15059         DisplayMessage("", _("Training mode on"));
15060       } else {
15061         gameMode = PlayFromGameFile;
15062         DisplayError(_("Already at end of game"), 0);
15063       }
15064     }
15065     ModeHighlight();
15066 }
15067
15068 void
15069 IcsClientEvent ()
15070 {
15071     if (!appData.icsActive) return;
15072     switch (gameMode) {
15073       case IcsPlayingWhite:
15074       case IcsPlayingBlack:
15075       case IcsObserving:
15076       case IcsIdle:
15077       case BeginningOfGame:
15078       case IcsExamining:
15079         return;
15080
15081       case EditGame:
15082         break;
15083
15084       case EditPosition:
15085         EditPositionDone(TRUE);
15086         break;
15087
15088       case AnalyzeMode:
15089       case AnalyzeFile:
15090         ExitAnalyzeMode();
15091         break;
15092
15093       default:
15094         EditGameEvent();
15095         break;
15096     }
15097
15098     gameMode = IcsIdle;
15099     ModeHighlight();
15100     return;
15101 }
15102
15103 void
15104 EditGameEvent ()
15105 {
15106     int i;
15107
15108     switch (gameMode) {
15109       case Training:
15110         SetTrainingModeOff();
15111         break;
15112       case MachinePlaysWhite:
15113       case MachinePlaysBlack:
15114       case BeginningOfGame:
15115         SendToProgram("force\n", &first);
15116         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15117             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15118                 char buf[MSG_SIZ];
15119                 abortEngineThink = TRUE;
15120                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15121                 SendToProgram(buf, &first);
15122                 DisplayMessage("Aborting engine think", "");
15123                 FreezeUI();
15124             }
15125         }
15126         SetUserThinkingEnables();
15127         break;
15128       case PlayFromGameFile:
15129         (void) StopLoadGameTimer();
15130         if (gameFileFP != NULL) {
15131             gameFileFP = NULL;
15132         }
15133         break;
15134       case EditPosition:
15135         EditPositionDone(TRUE);
15136         break;
15137       case AnalyzeMode:
15138       case AnalyzeFile:
15139         ExitAnalyzeMode();
15140         SendToProgram("force\n", &first);
15141         break;
15142       case TwoMachinesPlay:
15143         GameEnds(EndOfFile, NULL, GE_PLAYER);
15144         ResurrectChessProgram();
15145         SetUserThinkingEnables();
15146         break;
15147       case EndOfGame:
15148         ResurrectChessProgram();
15149         break;
15150       case IcsPlayingBlack:
15151       case IcsPlayingWhite:
15152         DisplayError(_("Warning: You are still playing a game"), 0);
15153         break;
15154       case IcsObserving:
15155         DisplayError(_("Warning: You are still observing a game"), 0);
15156         break;
15157       case IcsExamining:
15158         DisplayError(_("Warning: You are still examining a game"), 0);
15159         break;
15160       case IcsIdle:
15161         break;
15162       case EditGame:
15163       default:
15164         return;
15165     }
15166
15167     pausing = FALSE;
15168     StopClocks();
15169     first.offeredDraw = second.offeredDraw = 0;
15170
15171     if (gameMode == PlayFromGameFile) {
15172         whiteTimeRemaining = timeRemaining[0][currentMove];
15173         blackTimeRemaining = timeRemaining[1][currentMove];
15174         DisplayTitle("");
15175     }
15176
15177     if (gameMode == MachinePlaysWhite ||
15178         gameMode == MachinePlaysBlack ||
15179         gameMode == TwoMachinesPlay ||
15180         gameMode == EndOfGame) {
15181         i = forwardMostMove;
15182         while (i > currentMove) {
15183             SendToProgram("undo\n", &first);
15184             i--;
15185         }
15186         if(!adjustedClock) {
15187         whiteTimeRemaining = timeRemaining[0][currentMove];
15188         blackTimeRemaining = timeRemaining[1][currentMove];
15189         DisplayBothClocks();
15190         }
15191         if (whiteFlag || blackFlag) {
15192             whiteFlag = blackFlag = 0;
15193         }
15194         DisplayTitle("");
15195     }
15196
15197     gameMode = EditGame;
15198     ModeHighlight();
15199     SetGameInfo();
15200 }
15201
15202
15203 void
15204 EditPositionEvent ()
15205 {
15206     if (gameMode == EditPosition) {
15207         EditGameEvent();
15208         return;
15209     }
15210
15211     EditGameEvent();
15212     if (gameMode != EditGame) return;
15213
15214     gameMode = EditPosition;
15215     ModeHighlight();
15216     SetGameInfo();
15217     if (currentMove > 0)
15218       CopyBoard(boards[0], boards[currentMove]);
15219
15220     blackPlaysFirst = !WhiteOnMove(currentMove);
15221     ResetClocks();
15222     currentMove = forwardMostMove = backwardMostMove = 0;
15223     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15224     DisplayMove(-1);
15225     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15226 }
15227
15228 void
15229 ExitAnalyzeMode ()
15230 {
15231     /* [DM] icsEngineAnalyze - possible call from other functions */
15232     if (appData.icsEngineAnalyze) {
15233         appData.icsEngineAnalyze = FALSE;
15234
15235         DisplayMessage("",_("Close ICS engine analyze..."));
15236     }
15237     if (first.analysisSupport && first.analyzing) {
15238       SendToBoth("exit\n");
15239       first.analyzing = second.analyzing = FALSE;
15240     }
15241     thinkOutput[0] = NULLCHAR;
15242 }
15243
15244 void
15245 EditPositionDone (Boolean fakeRights)
15246 {
15247     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15248
15249     startedFromSetupPosition = TRUE;
15250     InitChessProgram(&first, FALSE);
15251     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15252       boards[0][EP_STATUS] = EP_NONE;
15253       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15254       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15255         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15256         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15257       } else boards[0][CASTLING][2] = NoRights;
15258       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15259         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15260         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15261       } else boards[0][CASTLING][5] = NoRights;
15262       if(gameInfo.variant == VariantSChess) {
15263         int i;
15264         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15265           boards[0][VIRGIN][i] = 0;
15266           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15267           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15268         }
15269       }
15270     }
15271     SendToProgram("force\n", &first);
15272     if (blackPlaysFirst) {
15273         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15274         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15275         currentMove = forwardMostMove = backwardMostMove = 1;
15276         CopyBoard(boards[1], boards[0]);
15277     } else {
15278         currentMove = forwardMostMove = backwardMostMove = 0;
15279     }
15280     SendBoard(&first, forwardMostMove);
15281     if (appData.debugMode) {
15282         fprintf(debugFP, "EditPosDone\n");
15283     }
15284     DisplayTitle("");
15285     DisplayMessage("", "");
15286     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15287     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15288     gameMode = EditGame;
15289     ModeHighlight();
15290     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15291     ClearHighlights(); /* [AS] */
15292 }
15293
15294 /* Pause for `ms' milliseconds */
15295 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15296 void
15297 TimeDelay (long ms)
15298 {
15299     TimeMark m1, m2;
15300
15301     GetTimeMark(&m1);
15302     do {
15303         GetTimeMark(&m2);
15304     } while (SubtractTimeMarks(&m2, &m1) < ms);
15305 }
15306
15307 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15308 void
15309 SendMultiLineToICS (char *buf)
15310 {
15311     char temp[MSG_SIZ+1], *p;
15312     int len;
15313
15314     len = strlen(buf);
15315     if (len > MSG_SIZ)
15316       len = MSG_SIZ;
15317
15318     strncpy(temp, buf, len);
15319     temp[len] = 0;
15320
15321     p = temp;
15322     while (*p) {
15323         if (*p == '\n' || *p == '\r')
15324           *p = ' ';
15325         ++p;
15326     }
15327
15328     strcat(temp, "\n");
15329     SendToICS(temp);
15330     SendToPlayer(temp, strlen(temp));
15331 }
15332
15333 void
15334 SetWhiteToPlayEvent ()
15335 {
15336     if (gameMode == EditPosition) {
15337         blackPlaysFirst = FALSE;
15338         DisplayBothClocks();    /* works because currentMove is 0 */
15339     } else if (gameMode == IcsExamining) {
15340         SendToICS(ics_prefix);
15341         SendToICS("tomove white\n");
15342     }
15343 }
15344
15345 void
15346 SetBlackToPlayEvent ()
15347 {
15348     if (gameMode == EditPosition) {
15349         blackPlaysFirst = TRUE;
15350         currentMove = 1;        /* kludge */
15351         DisplayBothClocks();
15352         currentMove = 0;
15353     } else if (gameMode == IcsExamining) {
15354         SendToICS(ics_prefix);
15355         SendToICS("tomove black\n");
15356     }
15357 }
15358
15359 void
15360 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15361 {
15362     char buf[MSG_SIZ];
15363     ChessSquare piece = boards[0][y][x];
15364     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15365     static int lastVariant;
15366
15367     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15368
15369     switch (selection) {
15370       case ClearBoard:
15371         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15372         MarkTargetSquares(1);
15373         CopyBoard(currentBoard, boards[0]);
15374         CopyBoard(menuBoard, initialPosition);
15375         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15376             SendToICS(ics_prefix);
15377             SendToICS("bsetup clear\n");
15378         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15379             SendToICS(ics_prefix);
15380             SendToICS("clearboard\n");
15381         } else {
15382             int nonEmpty = 0;
15383             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15384                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15385                 for (y = 0; y < BOARD_HEIGHT; y++) {
15386                     if (gameMode == IcsExamining) {
15387                         if (boards[currentMove][y][x] != EmptySquare) {
15388                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15389                                     AAA + x, ONE + y);
15390                             SendToICS(buf);
15391                         }
15392                     } else if(boards[0][y][x] != DarkSquare) {
15393                         if(boards[0][y][x] != p) nonEmpty++;
15394                         boards[0][y][x] = p;
15395                     }
15396                 }
15397             }
15398             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15399                 int r;
15400                 for(r = 0; r < BOARD_HEIGHT; r++) {
15401                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15402                     ChessSquare p = menuBoard[r][x];
15403                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15404                   }
15405                 }
15406                 DisplayMessage("Clicking clock again restores position", "");
15407                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15408                 if(!nonEmpty) { // asked to clear an empty board
15409                     CopyBoard(boards[0], menuBoard);
15410                 } else
15411                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15412                     CopyBoard(boards[0], initialPosition);
15413                 } else
15414                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15415                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15416                     CopyBoard(boards[0], erasedBoard);
15417                 } else
15418                     CopyBoard(erasedBoard, currentBoard);
15419
15420             }
15421         }
15422         if (gameMode == EditPosition) {
15423             DrawPosition(FALSE, boards[0]);
15424         }
15425         break;
15426
15427       case WhitePlay:
15428         SetWhiteToPlayEvent();
15429         break;
15430
15431       case BlackPlay:
15432         SetBlackToPlayEvent();
15433         break;
15434
15435       case EmptySquare:
15436         if (gameMode == IcsExamining) {
15437             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15438             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15439             SendToICS(buf);
15440         } else {
15441             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15442                 if(x == BOARD_LEFT-2) {
15443                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15444                     boards[0][y][1] = 0;
15445                 } else
15446                 if(x == BOARD_RGHT+1) {
15447                     if(y >= gameInfo.holdingsSize) break;
15448                     boards[0][y][BOARD_WIDTH-2] = 0;
15449                 } else break;
15450             }
15451             boards[0][y][x] = EmptySquare;
15452             DrawPosition(FALSE, boards[0]);
15453         }
15454         break;
15455
15456       case PromotePiece:
15457         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15458            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15459             selection = (ChessSquare) (PROMOTED(piece));
15460         } else if(piece == EmptySquare) selection = WhiteSilver;
15461         else selection = (ChessSquare)((int)piece - 1);
15462         goto defaultlabel;
15463
15464       case DemotePiece:
15465         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15466            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15467             selection = (ChessSquare) (DEMOTED(piece));
15468         } else if(piece == EmptySquare) selection = BlackSilver;
15469         else selection = (ChessSquare)((int)piece + 1);
15470         goto defaultlabel;
15471
15472       case WhiteQueen:
15473       case BlackQueen:
15474         if(gameInfo.variant == VariantShatranj ||
15475            gameInfo.variant == VariantXiangqi  ||
15476            gameInfo.variant == VariantCourier  ||
15477            gameInfo.variant == VariantASEAN    ||
15478            gameInfo.variant == VariantMakruk     )
15479             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15480         goto defaultlabel;
15481
15482       case WhiteKing:
15483       case BlackKing:
15484         if(gameInfo.variant == VariantXiangqi)
15485             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15486         if(gameInfo.variant == VariantKnightmate)
15487             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15488       default:
15489         defaultlabel:
15490         if (gameMode == IcsExamining) {
15491             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15492             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15493                      PieceToChar(selection), AAA + x, ONE + y);
15494             SendToICS(buf);
15495         } else {
15496             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15497                 int n;
15498                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15499                     n = PieceToNumber(selection - BlackPawn);
15500                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15501                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15502                     boards[0][BOARD_HEIGHT-1-n][1]++;
15503                 } else
15504                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15505                     n = PieceToNumber(selection);
15506                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15507                     boards[0][n][BOARD_WIDTH-1] = selection;
15508                     boards[0][n][BOARD_WIDTH-2]++;
15509                 }
15510             } else
15511             boards[0][y][x] = selection;
15512             DrawPosition(TRUE, boards[0]);
15513             ClearHighlights();
15514             fromX = fromY = -1;
15515         }
15516         break;
15517     }
15518 }
15519
15520
15521 void
15522 DropMenuEvent (ChessSquare selection, int x, int y)
15523 {
15524     ChessMove moveType;
15525
15526     switch (gameMode) {
15527       case IcsPlayingWhite:
15528       case MachinePlaysBlack:
15529         if (!WhiteOnMove(currentMove)) {
15530             DisplayMoveError(_("It is Black's turn"));
15531             return;
15532         }
15533         moveType = WhiteDrop;
15534         break;
15535       case IcsPlayingBlack:
15536       case MachinePlaysWhite:
15537         if (WhiteOnMove(currentMove)) {
15538             DisplayMoveError(_("It is White's turn"));
15539             return;
15540         }
15541         moveType = BlackDrop;
15542         break;
15543       case EditGame:
15544         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15545         break;
15546       default:
15547         return;
15548     }
15549
15550     if (moveType == BlackDrop && selection < BlackPawn) {
15551       selection = (ChessSquare) ((int) selection
15552                                  + (int) BlackPawn - (int) WhitePawn);
15553     }
15554     if (boards[currentMove][y][x] != EmptySquare) {
15555         DisplayMoveError(_("That square is occupied"));
15556         return;
15557     }
15558
15559     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15560 }
15561
15562 void
15563 AcceptEvent ()
15564 {
15565     /* Accept a pending offer of any kind from opponent */
15566
15567     if (appData.icsActive) {
15568         SendToICS(ics_prefix);
15569         SendToICS("accept\n");
15570     } else if (cmailMsgLoaded) {
15571         if (currentMove == cmailOldMove &&
15572             commentList[cmailOldMove] != NULL &&
15573             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15574                    "Black offers a draw" : "White offers a draw")) {
15575             TruncateGame();
15576             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15577             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15578         } else {
15579             DisplayError(_("There is no pending offer on this move"), 0);
15580             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15581         }
15582     } else {
15583         /* Not used for offers from chess program */
15584     }
15585 }
15586
15587 void
15588 DeclineEvent ()
15589 {
15590     /* Decline a pending offer of any kind from opponent */
15591
15592     if (appData.icsActive) {
15593         SendToICS(ics_prefix);
15594         SendToICS("decline\n");
15595     } else if (cmailMsgLoaded) {
15596         if (currentMove == cmailOldMove &&
15597             commentList[cmailOldMove] != NULL &&
15598             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15599                    "Black offers a draw" : "White offers a draw")) {
15600 #ifdef NOTDEF
15601             AppendComment(cmailOldMove, "Draw declined", TRUE);
15602             DisplayComment(cmailOldMove - 1, "Draw declined");
15603 #endif /*NOTDEF*/
15604         } else {
15605             DisplayError(_("There is no pending offer on this move"), 0);
15606         }
15607     } else {
15608         /* Not used for offers from chess program */
15609     }
15610 }
15611
15612 void
15613 RematchEvent ()
15614 {
15615     /* Issue ICS rematch command */
15616     if (appData.icsActive) {
15617         SendToICS(ics_prefix);
15618         SendToICS("rematch\n");
15619     }
15620 }
15621
15622 void
15623 CallFlagEvent ()
15624 {
15625     /* Call your opponent's flag (claim a win on time) */
15626     if (appData.icsActive) {
15627         SendToICS(ics_prefix);
15628         SendToICS("flag\n");
15629     } else {
15630         switch (gameMode) {
15631           default:
15632             return;
15633           case MachinePlaysWhite:
15634             if (whiteFlag) {
15635                 if (blackFlag)
15636                   GameEnds(GameIsDrawn, "Both players ran out of time",
15637                            GE_PLAYER);
15638                 else
15639                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15640             } else {
15641                 DisplayError(_("Your opponent is not out of time"), 0);
15642             }
15643             break;
15644           case MachinePlaysBlack:
15645             if (blackFlag) {
15646                 if (whiteFlag)
15647                   GameEnds(GameIsDrawn, "Both players ran out of time",
15648                            GE_PLAYER);
15649                 else
15650                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15651             } else {
15652                 DisplayError(_("Your opponent is not out of time"), 0);
15653             }
15654             break;
15655         }
15656     }
15657 }
15658
15659 void
15660 ClockClick (int which)
15661 {       // [HGM] code moved to back-end from winboard.c
15662         if(which) { // black clock
15663           if (gameMode == EditPosition || gameMode == IcsExamining) {
15664             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15665             SetBlackToPlayEvent();
15666           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15667                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15668           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15669           } else if (shiftKey) {
15670             AdjustClock(which, -1);
15671           } else if (gameMode == IcsPlayingWhite ||
15672                      gameMode == MachinePlaysBlack) {
15673             CallFlagEvent();
15674           }
15675         } else { // white clock
15676           if (gameMode == EditPosition || gameMode == IcsExamining) {
15677             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15678             SetWhiteToPlayEvent();
15679           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15680                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15681           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15682           } else if (shiftKey) {
15683             AdjustClock(which, -1);
15684           } else if (gameMode == IcsPlayingBlack ||
15685                    gameMode == MachinePlaysWhite) {
15686             CallFlagEvent();
15687           }
15688         }
15689 }
15690
15691 void
15692 DrawEvent ()
15693 {
15694     /* Offer draw or accept pending draw offer from opponent */
15695
15696     if (appData.icsActive) {
15697         /* Note: tournament rules require draw offers to be
15698            made after you make your move but before you punch
15699            your clock.  Currently ICS doesn't let you do that;
15700            instead, you immediately punch your clock after making
15701            a move, but you can offer a draw at any time. */
15702
15703         SendToICS(ics_prefix);
15704         SendToICS("draw\n");
15705         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15706     } else if (cmailMsgLoaded) {
15707         if (currentMove == cmailOldMove &&
15708             commentList[cmailOldMove] != NULL &&
15709             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15710                    "Black offers a draw" : "White offers a draw")) {
15711             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15712             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15713         } else if (currentMove == cmailOldMove + 1) {
15714             char *offer = WhiteOnMove(cmailOldMove) ?
15715               "White offers a draw" : "Black offers a draw";
15716             AppendComment(currentMove, offer, TRUE);
15717             DisplayComment(currentMove - 1, offer);
15718             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15719         } else {
15720             DisplayError(_("You must make your move before offering a draw"), 0);
15721             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15722         }
15723     } else if (first.offeredDraw) {
15724         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15725     } else {
15726         if (first.sendDrawOffers) {
15727             SendToProgram("draw\n", &first);
15728             userOfferedDraw = TRUE;
15729         }
15730     }
15731 }
15732
15733 void
15734 AdjournEvent ()
15735 {
15736     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15737
15738     if (appData.icsActive) {
15739         SendToICS(ics_prefix);
15740         SendToICS("adjourn\n");
15741     } else {
15742         /* Currently GNU Chess doesn't offer or accept Adjourns */
15743     }
15744 }
15745
15746
15747 void
15748 AbortEvent ()
15749 {
15750     /* Offer Abort or accept pending Abort offer from opponent */
15751
15752     if (appData.icsActive) {
15753         SendToICS(ics_prefix);
15754         SendToICS("abort\n");
15755     } else {
15756         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15757     }
15758 }
15759
15760 void
15761 ResignEvent ()
15762 {
15763     /* Resign.  You can do this even if it's not your turn. */
15764
15765     if (appData.icsActive) {
15766         SendToICS(ics_prefix);
15767         SendToICS("resign\n");
15768     } else {
15769         switch (gameMode) {
15770           case MachinePlaysWhite:
15771             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15772             break;
15773           case MachinePlaysBlack:
15774             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15775             break;
15776           case EditGame:
15777             if (cmailMsgLoaded) {
15778                 TruncateGame();
15779                 if (WhiteOnMove(cmailOldMove)) {
15780                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15781                 } else {
15782                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15783                 }
15784                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15785             }
15786             break;
15787           default:
15788             break;
15789         }
15790     }
15791 }
15792
15793
15794 void
15795 StopObservingEvent ()
15796 {
15797     /* Stop observing current games */
15798     SendToICS(ics_prefix);
15799     SendToICS("unobserve\n");
15800 }
15801
15802 void
15803 StopExaminingEvent ()
15804 {
15805     /* Stop observing current game */
15806     SendToICS(ics_prefix);
15807     SendToICS("unexamine\n");
15808 }
15809
15810 void
15811 ForwardInner (int target)
15812 {
15813     int limit; int oldSeekGraphUp = seekGraphUp;
15814
15815     if (appData.debugMode)
15816         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15817                 target, currentMove, forwardMostMove);
15818
15819     if (gameMode == EditPosition)
15820       return;
15821
15822     seekGraphUp = FALSE;
15823     MarkTargetSquares(1);
15824     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15825
15826     if (gameMode == PlayFromGameFile && !pausing)
15827       PauseEvent();
15828
15829     if (gameMode == IcsExamining && pausing)
15830       limit = pauseExamForwardMostMove;
15831     else
15832       limit = forwardMostMove;
15833
15834     if (target > limit) target = limit;
15835
15836     if (target > 0 && moveList[target - 1][0]) {
15837         int fromX, fromY, toX, toY;
15838         toX = moveList[target - 1][2] - AAA;
15839         toY = moveList[target - 1][3] - ONE;
15840         if (moveList[target - 1][1] == '@') {
15841             if (appData.highlightLastMove) {
15842                 SetHighlights(-1, -1, toX, toY);
15843             }
15844         } else {
15845             int viaX = moveList[target - 1][5] - AAA;
15846             int viaY = moveList[target - 1][6] - ONE;
15847             fromX = moveList[target - 1][0] - AAA;
15848             fromY = moveList[target - 1][1] - ONE;
15849             if (target == currentMove + 1) {
15850                 if(moveList[target - 1][4] == ';') { // multi-leg
15851                     ChessSquare piece = boards[currentMove][viaY][viaX];
15852                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15853                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15854                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15855                     boards[currentMove][viaY][viaX] = piece;
15856                 } else
15857                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15858             }
15859             if (appData.highlightLastMove) {
15860                 SetHighlights(fromX, fromY, toX, toY);
15861             }
15862         }
15863     }
15864     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15865         gameMode == Training || gameMode == PlayFromGameFile ||
15866         gameMode == AnalyzeFile) {
15867         while (currentMove < target) {
15868             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15869             SendMoveToProgram(currentMove++, &first);
15870         }
15871     } else {
15872         currentMove = target;
15873     }
15874
15875     if (gameMode == EditGame || gameMode == EndOfGame) {
15876         whiteTimeRemaining = timeRemaining[0][currentMove];
15877         blackTimeRemaining = timeRemaining[1][currentMove];
15878     }
15879     DisplayBothClocks();
15880     DisplayMove(currentMove - 1);
15881     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15882     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15883     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15884         DisplayComment(currentMove - 1, commentList[currentMove]);
15885     }
15886     ClearMap(); // [HGM] exclude: invalidate map
15887 }
15888
15889
15890 void
15891 ForwardEvent ()
15892 {
15893     if (gameMode == IcsExamining && !pausing) {
15894         SendToICS(ics_prefix);
15895         SendToICS("forward\n");
15896     } else {
15897         ForwardInner(currentMove + 1);
15898     }
15899 }
15900
15901 void
15902 ToEndEvent ()
15903 {
15904     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15905         /* to optimze, we temporarily turn off analysis mode while we feed
15906          * the remaining moves to the engine. Otherwise we get analysis output
15907          * after each move.
15908          */
15909         if (first.analysisSupport) {
15910           SendToProgram("exit\nforce\n", &first);
15911           first.analyzing = FALSE;
15912         }
15913     }
15914
15915     if (gameMode == IcsExamining && !pausing) {
15916         SendToICS(ics_prefix);
15917         SendToICS("forward 999999\n");
15918     } else {
15919         ForwardInner(forwardMostMove);
15920     }
15921
15922     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15923         /* we have fed all the moves, so reactivate analysis mode */
15924         SendToProgram("analyze\n", &first);
15925         first.analyzing = TRUE;
15926         /*first.maybeThinking = TRUE;*/
15927         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15928     }
15929 }
15930
15931 void
15932 BackwardInner (int target)
15933 {
15934     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15935
15936     if (appData.debugMode)
15937         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15938                 target, currentMove, forwardMostMove);
15939
15940     if (gameMode == EditPosition) return;
15941     seekGraphUp = FALSE;
15942     MarkTargetSquares(1);
15943     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15944     if (currentMove <= backwardMostMove) {
15945         ClearHighlights();
15946         DrawPosition(full_redraw, boards[currentMove]);
15947         return;
15948     }
15949     if (gameMode == PlayFromGameFile && !pausing)
15950       PauseEvent();
15951
15952     if (moveList[target][0]) {
15953         int fromX, fromY, toX, toY;
15954         toX = moveList[target][2] - AAA;
15955         toY = moveList[target][3] - ONE;
15956         if (moveList[target][1] == '@') {
15957             if (appData.highlightLastMove) {
15958                 SetHighlights(-1, -1, toX, toY);
15959             }
15960         } else {
15961             fromX = moveList[target][0] - AAA;
15962             fromY = moveList[target][1] - ONE;
15963             if (target == currentMove - 1) {
15964                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15965             }
15966             if (appData.highlightLastMove) {
15967                 SetHighlights(fromX, fromY, toX, toY);
15968             }
15969         }
15970     }
15971     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15972         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15973         while (currentMove > target) {
15974             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15975                 // null move cannot be undone. Reload program with move history before it.
15976                 int i;
15977                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15978                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15979                 }
15980                 SendBoard(&first, i);
15981               if(second.analyzing) SendBoard(&second, i);
15982                 for(currentMove=i; currentMove<target; currentMove++) {
15983                     SendMoveToProgram(currentMove, &first);
15984                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15985                 }
15986                 break;
15987             }
15988             SendToBoth("undo\n");
15989             currentMove--;
15990         }
15991     } else {
15992         currentMove = target;
15993     }
15994
15995     if (gameMode == EditGame || gameMode == EndOfGame) {
15996         whiteTimeRemaining = timeRemaining[0][currentMove];
15997         blackTimeRemaining = timeRemaining[1][currentMove];
15998     }
15999     DisplayBothClocks();
16000     DisplayMove(currentMove - 1);
16001     DrawPosition(full_redraw, boards[currentMove]);
16002     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16003     // [HGM] PV info: routine tests if comment empty
16004     DisplayComment(currentMove - 1, commentList[currentMove]);
16005     ClearMap(); // [HGM] exclude: invalidate map
16006 }
16007
16008 void
16009 BackwardEvent ()
16010 {
16011     if (gameMode == IcsExamining && !pausing) {
16012         SendToICS(ics_prefix);
16013         SendToICS("backward\n");
16014     } else {
16015         BackwardInner(currentMove - 1);
16016     }
16017 }
16018
16019 void
16020 ToStartEvent ()
16021 {
16022     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16023         /* to optimize, we temporarily turn off analysis mode while we undo
16024          * all the moves. Otherwise we get analysis output after each undo.
16025          */
16026         if (first.analysisSupport) {
16027           SendToProgram("exit\nforce\n", &first);
16028           first.analyzing = FALSE;
16029         }
16030     }
16031
16032     if (gameMode == IcsExamining && !pausing) {
16033         SendToICS(ics_prefix);
16034         SendToICS("backward 999999\n");
16035     } else {
16036         BackwardInner(backwardMostMove);
16037     }
16038
16039     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16040         /* we have fed all the moves, so reactivate analysis mode */
16041         SendToProgram("analyze\n", &first);
16042         first.analyzing = TRUE;
16043         /*first.maybeThinking = TRUE;*/
16044         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16045     }
16046 }
16047
16048 void
16049 ToNrEvent (int to)
16050 {
16051   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16052   if (to >= forwardMostMove) to = forwardMostMove;
16053   if (to <= backwardMostMove) to = backwardMostMove;
16054   if (to < currentMove) {
16055     BackwardInner(to);
16056   } else {
16057     ForwardInner(to);
16058   }
16059 }
16060
16061 void
16062 RevertEvent (Boolean annotate)
16063 {
16064     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16065         return;
16066     }
16067     if (gameMode != IcsExamining) {
16068         DisplayError(_("You are not examining a game"), 0);
16069         return;
16070     }
16071     if (pausing) {
16072         DisplayError(_("You can't revert while pausing"), 0);
16073         return;
16074     }
16075     SendToICS(ics_prefix);
16076     SendToICS("revert\n");
16077 }
16078
16079 void
16080 RetractMoveEvent ()
16081 {
16082     switch (gameMode) {
16083       case MachinePlaysWhite:
16084       case MachinePlaysBlack:
16085         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16086             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16087             return;
16088         }
16089         if (forwardMostMove < 2) return;
16090         currentMove = forwardMostMove = forwardMostMove - 2;
16091         whiteTimeRemaining = timeRemaining[0][currentMove];
16092         blackTimeRemaining = timeRemaining[1][currentMove];
16093         DisplayBothClocks();
16094         DisplayMove(currentMove - 1);
16095         ClearHighlights();/*!! could figure this out*/
16096         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16097         SendToProgram("remove\n", &first);
16098         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16099         break;
16100
16101       case BeginningOfGame:
16102       default:
16103         break;
16104
16105       case IcsPlayingWhite:
16106       case IcsPlayingBlack:
16107         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16108             SendToICS(ics_prefix);
16109             SendToICS("takeback 2\n");
16110         } else {
16111             SendToICS(ics_prefix);
16112             SendToICS("takeback 1\n");
16113         }
16114         break;
16115     }
16116 }
16117
16118 void
16119 MoveNowEvent ()
16120 {
16121     ChessProgramState *cps;
16122
16123     switch (gameMode) {
16124       case MachinePlaysWhite:
16125         if (!WhiteOnMove(forwardMostMove)) {
16126             DisplayError(_("It is your turn"), 0);
16127             return;
16128         }
16129         cps = &first;
16130         break;
16131       case MachinePlaysBlack:
16132         if (WhiteOnMove(forwardMostMove)) {
16133             DisplayError(_("It is your turn"), 0);
16134             return;
16135         }
16136         cps = &first;
16137         break;
16138       case TwoMachinesPlay:
16139         if (WhiteOnMove(forwardMostMove) ==
16140             (first.twoMachinesColor[0] == 'w')) {
16141             cps = &first;
16142         } else {
16143             cps = &second;
16144         }
16145         break;
16146       case BeginningOfGame:
16147       default:
16148         return;
16149     }
16150     SendToProgram("?\n", cps);
16151 }
16152
16153 void
16154 TruncateGameEvent ()
16155 {
16156     EditGameEvent();
16157     if (gameMode != EditGame) return;
16158     TruncateGame();
16159 }
16160
16161 void
16162 TruncateGame ()
16163 {
16164     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16165     if (forwardMostMove > currentMove) {
16166         if (gameInfo.resultDetails != NULL) {
16167             free(gameInfo.resultDetails);
16168             gameInfo.resultDetails = NULL;
16169             gameInfo.result = GameUnfinished;
16170         }
16171         forwardMostMove = currentMove;
16172         HistorySet(parseList, backwardMostMove, forwardMostMove,
16173                    currentMove-1);
16174     }
16175 }
16176
16177 void
16178 HintEvent ()
16179 {
16180     if (appData.noChessProgram) return;
16181     switch (gameMode) {
16182       case MachinePlaysWhite:
16183         if (WhiteOnMove(forwardMostMove)) {
16184             DisplayError(_("Wait until your turn."), 0);
16185             return;
16186         }
16187         break;
16188       case BeginningOfGame:
16189       case MachinePlaysBlack:
16190         if (!WhiteOnMove(forwardMostMove)) {
16191             DisplayError(_("Wait until your turn."), 0);
16192             return;
16193         }
16194         break;
16195       default:
16196         DisplayError(_("No hint available"), 0);
16197         return;
16198     }
16199     SendToProgram("hint\n", &first);
16200     hintRequested = TRUE;
16201 }
16202
16203 int
16204 SaveSelected (FILE *g, int dummy, char *dummy2)
16205 {
16206     ListGame * lg = (ListGame *) gameList.head;
16207     int nItem, cnt=0;
16208     FILE *f;
16209
16210     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16211         DisplayError(_("Game list not loaded or empty"), 0);
16212         return 0;
16213     }
16214
16215     creatingBook = TRUE; // suppresses stuff during load game
16216
16217     /* Get list size */
16218     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16219         if(lg->position >= 0) { // selected?
16220             LoadGame(f, nItem, "", TRUE);
16221             SaveGamePGN2(g); // leaves g open
16222             cnt++; DoEvents();
16223         }
16224         lg = (ListGame *) lg->node.succ;
16225     }
16226
16227     fclose(g);
16228     creatingBook = FALSE;
16229
16230     return cnt;
16231 }
16232
16233 void
16234 CreateBookEvent ()
16235 {
16236     ListGame * lg = (ListGame *) gameList.head;
16237     FILE *f, *g;
16238     int nItem;
16239     static int secondTime = FALSE;
16240
16241     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16242         DisplayError(_("Game list not loaded or empty"), 0);
16243         return;
16244     }
16245
16246     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16247         fclose(g);
16248         secondTime++;
16249         DisplayNote(_("Book file exists! Try again for overwrite."));
16250         return;
16251     }
16252
16253     creatingBook = TRUE;
16254     secondTime = FALSE;
16255
16256     /* Get list size */
16257     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16258         if(lg->position >= 0) {
16259             LoadGame(f, nItem, "", TRUE);
16260             AddGameToBook(TRUE);
16261             DoEvents();
16262         }
16263         lg = (ListGame *) lg->node.succ;
16264     }
16265
16266     creatingBook = FALSE;
16267     FlushBook();
16268 }
16269
16270 void
16271 BookEvent ()
16272 {
16273     if (appData.noChessProgram) return;
16274     switch (gameMode) {
16275       case MachinePlaysWhite:
16276         if (WhiteOnMove(forwardMostMove)) {
16277             DisplayError(_("Wait until your turn."), 0);
16278             return;
16279         }
16280         break;
16281       case BeginningOfGame:
16282       case MachinePlaysBlack:
16283         if (!WhiteOnMove(forwardMostMove)) {
16284             DisplayError(_("Wait until your turn."), 0);
16285             return;
16286         }
16287         break;
16288       case EditPosition:
16289         EditPositionDone(TRUE);
16290         break;
16291       case TwoMachinesPlay:
16292         return;
16293       default:
16294         break;
16295     }
16296     SendToProgram("bk\n", &first);
16297     bookOutput[0] = NULLCHAR;
16298     bookRequested = TRUE;
16299 }
16300
16301 void
16302 AboutGameEvent ()
16303 {
16304     char *tags = PGNTags(&gameInfo);
16305     TagsPopUp(tags, CmailMsg());
16306     free(tags);
16307 }
16308
16309 /* end button procedures */
16310
16311 void
16312 PrintPosition (FILE *fp, int move)
16313 {
16314     int i, j;
16315
16316     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16317         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16318             char c = PieceToChar(boards[move][i][j]);
16319             fputc(c == '?' ? '.' : c, fp);
16320             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16321         }
16322     }
16323     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16324       fprintf(fp, "white to play\n");
16325     else
16326       fprintf(fp, "black to play\n");
16327 }
16328
16329 void
16330 PrintOpponents (FILE *fp)
16331 {
16332     if (gameInfo.white != NULL) {
16333         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16334     } else {
16335         fprintf(fp, "\n");
16336     }
16337 }
16338
16339 /* Find last component of program's own name, using some heuristics */
16340 void
16341 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16342 {
16343     char *p, *q, c;
16344     int local = (strcmp(host, "localhost") == 0);
16345     while (!local && (p = strchr(prog, ';')) != NULL) {
16346         p++;
16347         while (*p == ' ') p++;
16348         prog = p;
16349     }
16350     if (*prog == '"' || *prog == '\'') {
16351         q = strchr(prog + 1, *prog);
16352     } else {
16353         q = strchr(prog, ' ');
16354     }
16355     if (q == NULL) q = prog + strlen(prog);
16356     p = q;
16357     while (p >= prog && *p != '/' && *p != '\\') p--;
16358     p++;
16359     if(p == prog && *p == '"') p++;
16360     c = *q; *q = 0;
16361     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16362     memcpy(buf, p, q - p);
16363     buf[q - p] = NULLCHAR;
16364     if (!local) {
16365         strcat(buf, "@");
16366         strcat(buf, host);
16367     }
16368 }
16369
16370 char *
16371 TimeControlTagValue ()
16372 {
16373     char buf[MSG_SIZ];
16374     if (!appData.clockMode) {
16375       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16376     } else if (movesPerSession > 0) {
16377       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16378     } else if (timeIncrement == 0) {
16379       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16380     } else {
16381       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16382     }
16383     return StrSave(buf);
16384 }
16385
16386 void
16387 SetGameInfo ()
16388 {
16389     /* This routine is used only for certain modes */
16390     VariantClass v = gameInfo.variant;
16391     ChessMove r = GameUnfinished;
16392     char *p = NULL;
16393
16394     if(keepInfo) return;
16395
16396     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16397         r = gameInfo.result;
16398         p = gameInfo.resultDetails;
16399         gameInfo.resultDetails = NULL;
16400     }
16401     ClearGameInfo(&gameInfo);
16402     gameInfo.variant = v;
16403
16404     switch (gameMode) {
16405       case MachinePlaysWhite:
16406         gameInfo.event = StrSave( appData.pgnEventHeader );
16407         gameInfo.site = StrSave(HostName());
16408         gameInfo.date = PGNDate();
16409         gameInfo.round = StrSave("-");
16410         gameInfo.white = StrSave(first.tidy);
16411         gameInfo.black = StrSave(UserName());
16412         gameInfo.timeControl = TimeControlTagValue();
16413         break;
16414
16415       case MachinePlaysBlack:
16416         gameInfo.event = StrSave( appData.pgnEventHeader );
16417         gameInfo.site = StrSave(HostName());
16418         gameInfo.date = PGNDate();
16419         gameInfo.round = StrSave("-");
16420         gameInfo.white = StrSave(UserName());
16421         gameInfo.black = StrSave(first.tidy);
16422         gameInfo.timeControl = TimeControlTagValue();
16423         break;
16424
16425       case TwoMachinesPlay:
16426         gameInfo.event = StrSave( appData.pgnEventHeader );
16427         gameInfo.site = StrSave(HostName());
16428         gameInfo.date = PGNDate();
16429         if (roundNr > 0) {
16430             char buf[MSG_SIZ];
16431             snprintf(buf, MSG_SIZ, "%d", roundNr);
16432             gameInfo.round = StrSave(buf);
16433         } else {
16434             gameInfo.round = StrSave("-");
16435         }
16436         if (first.twoMachinesColor[0] == 'w') {
16437             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16438             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16439         } else {
16440             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16441             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16442         }
16443         gameInfo.timeControl = TimeControlTagValue();
16444         break;
16445
16446       case EditGame:
16447         gameInfo.event = StrSave("Edited game");
16448         gameInfo.site = StrSave(HostName());
16449         gameInfo.date = PGNDate();
16450         gameInfo.round = StrSave("-");
16451         gameInfo.white = StrSave("-");
16452         gameInfo.black = StrSave("-");
16453         gameInfo.result = r;
16454         gameInfo.resultDetails = p;
16455         break;
16456
16457       case EditPosition:
16458         gameInfo.event = StrSave("Edited position");
16459         gameInfo.site = StrSave(HostName());
16460         gameInfo.date = PGNDate();
16461         gameInfo.round = StrSave("-");
16462         gameInfo.white = StrSave("-");
16463         gameInfo.black = StrSave("-");
16464         break;
16465
16466       case IcsPlayingWhite:
16467       case IcsPlayingBlack:
16468       case IcsObserving:
16469       case IcsExamining:
16470         break;
16471
16472       case PlayFromGameFile:
16473         gameInfo.event = StrSave("Game from non-PGN file");
16474         gameInfo.site = StrSave(HostName());
16475         gameInfo.date = PGNDate();
16476         gameInfo.round = StrSave("-");
16477         gameInfo.white = StrSave("?");
16478         gameInfo.black = StrSave("?");
16479         break;
16480
16481       default:
16482         break;
16483     }
16484 }
16485
16486 void
16487 ReplaceComment (int index, char *text)
16488 {
16489     int len;
16490     char *p;
16491     float score;
16492
16493     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16494        pvInfoList[index-1].depth == len &&
16495        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16496        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16497     while (*text == '\n') text++;
16498     len = strlen(text);
16499     while (len > 0 && text[len - 1] == '\n') len--;
16500
16501     if (commentList[index] != NULL)
16502       free(commentList[index]);
16503
16504     if (len == 0) {
16505         commentList[index] = NULL;
16506         return;
16507     }
16508   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16509       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16510       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16511     commentList[index] = (char *) malloc(len + 2);
16512     strncpy(commentList[index], text, len);
16513     commentList[index][len] = '\n';
16514     commentList[index][len + 1] = NULLCHAR;
16515   } else {
16516     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16517     char *p;
16518     commentList[index] = (char *) malloc(len + 7);
16519     safeStrCpy(commentList[index], "{\n", 3);
16520     safeStrCpy(commentList[index]+2, text, len+1);
16521     commentList[index][len+2] = NULLCHAR;
16522     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16523     strcat(commentList[index], "\n}\n");
16524   }
16525 }
16526
16527 void
16528 CrushCRs (char *text)
16529 {
16530   char *p = text;
16531   char *q = text;
16532   char ch;
16533
16534   do {
16535     ch = *p++;
16536     if (ch == '\r') continue;
16537     *q++ = ch;
16538   } while (ch != '\0');
16539 }
16540
16541 void
16542 AppendComment (int index, char *text, Boolean addBraces)
16543 /* addBraces  tells if we should add {} */
16544 {
16545     int oldlen, len;
16546     char *old;
16547
16548 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16549     if(addBraces == 3) addBraces = 0; else // force appending literally
16550     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16551
16552     CrushCRs(text);
16553     while (*text == '\n') text++;
16554     len = strlen(text);
16555     while (len > 0 && text[len - 1] == '\n') len--;
16556     text[len] = NULLCHAR;
16557
16558     if (len == 0) return;
16559
16560     if (commentList[index] != NULL) {
16561       Boolean addClosingBrace = addBraces;
16562         old = commentList[index];
16563         oldlen = strlen(old);
16564         while(commentList[index][oldlen-1] ==  '\n')
16565           commentList[index][--oldlen] = NULLCHAR;
16566         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16567         safeStrCpy(commentList[index], old, oldlen + len + 6);
16568         free(old);
16569         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16570         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16571           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16572           while (*text == '\n') { text++; len--; }
16573           commentList[index][--oldlen] = NULLCHAR;
16574       }
16575         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16576         else          strcat(commentList[index], "\n");
16577         strcat(commentList[index], text);
16578         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16579         else          strcat(commentList[index], "\n");
16580     } else {
16581         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16582         if(addBraces)
16583           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16584         else commentList[index][0] = NULLCHAR;
16585         strcat(commentList[index], text);
16586         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16587         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16588     }
16589 }
16590
16591 static char *
16592 FindStr (char * text, char * sub_text)
16593 {
16594     char * result = strstr( text, sub_text );
16595
16596     if( result != NULL ) {
16597         result += strlen( sub_text );
16598     }
16599
16600     return result;
16601 }
16602
16603 /* [AS] Try to extract PV info from PGN comment */
16604 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16605 char *
16606 GetInfoFromComment (int index, char * text)
16607 {
16608     char * sep = text, *p;
16609
16610     if( text != NULL && index > 0 ) {
16611         int score = 0;
16612         int depth = 0;
16613         int time = -1, sec = 0, deci;
16614         char * s_eval = FindStr( text, "[%eval " );
16615         char * s_emt = FindStr( text, "[%emt " );
16616 #if 0
16617         if( s_eval != NULL || s_emt != NULL ) {
16618 #else
16619         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16620 #endif
16621             /* New style */
16622             char delim;
16623
16624             if( s_eval != NULL ) {
16625                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16626                     return text;
16627                 }
16628
16629                 if( delim != ']' ) {
16630                     return text;
16631                 }
16632             }
16633
16634             if( s_emt != NULL ) {
16635             }
16636                 return text;
16637         }
16638         else {
16639             /* We expect something like: [+|-]nnn.nn/dd */
16640             int score_lo = 0;
16641
16642             if(*text != '{') return text; // [HGM] braces: must be normal comment
16643
16644             sep = strchr( text, '/' );
16645             if( sep == NULL || sep < (text+4) ) {
16646                 return text;
16647             }
16648
16649             p = text;
16650             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16651             if(p[1] == '(') { // comment starts with PV
16652                p = strchr(p, ')'); // locate end of PV
16653                if(p == NULL || sep < p+5) return text;
16654                // at this point we have something like "{(.*) +0.23/6 ..."
16655                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16656                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16657                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16658             }
16659             time = -1; sec = -1; deci = -1;
16660             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16661                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16662                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16663                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16664                 return text;
16665             }
16666
16667             if( score_lo < 0 || score_lo >= 100 ) {
16668                 return text;
16669             }
16670
16671             if(sec >= 0) time = 600*time + 10*sec; else
16672             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16673
16674             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16675
16676             /* [HGM] PV time: now locate end of PV info */
16677             while( *++sep >= '0' && *sep <= '9'); // strip depth
16678             if(time >= 0)
16679             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16680             if(sec >= 0)
16681             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16682             if(deci >= 0)
16683             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16684             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16685         }
16686
16687         if( depth <= 0 ) {
16688             return text;
16689         }
16690
16691         if( time < 0 ) {
16692             time = -1;
16693         }
16694
16695         pvInfoList[index-1].depth = depth;
16696         pvInfoList[index-1].score = score;
16697         pvInfoList[index-1].time  = 10*time; // centi-sec
16698         if(*sep == '}') *sep = 0; else *--sep = '{';
16699         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16700     }
16701     return sep;
16702 }
16703
16704 void
16705 SendToProgram (char *message, ChessProgramState *cps)
16706 {
16707     int count, outCount, error;
16708     char buf[MSG_SIZ];
16709
16710     if (cps->pr == NoProc) return;
16711     Attention(cps);
16712
16713     if (appData.debugMode) {
16714         TimeMark now;
16715         GetTimeMark(&now);
16716         fprintf(debugFP, "%ld >%-6s: %s",
16717                 SubtractTimeMarks(&now, &programStartTime),
16718                 cps->which, message);
16719         if(serverFP)
16720             fprintf(serverFP, "%ld >%-6s: %s",
16721                 SubtractTimeMarks(&now, &programStartTime),
16722                 cps->which, message), fflush(serverFP);
16723     }
16724
16725     count = strlen(message);
16726     outCount = OutputToProcess(cps->pr, message, count, &error);
16727     if (outCount < count && !exiting
16728                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16729       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16730       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16731         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16732             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16733                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16734                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16735                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16736             } else {
16737                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16738                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16739                 gameInfo.result = res;
16740             }
16741             gameInfo.resultDetails = StrSave(buf);
16742         }
16743         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16744         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16745     }
16746 }
16747
16748 void
16749 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16750 {
16751     char *end_str;
16752     char buf[MSG_SIZ];
16753     ChessProgramState *cps = (ChessProgramState *)closure;
16754
16755     if (isr != cps->isr) return; /* Killed intentionally */
16756     if (count <= 0) {
16757         if (count == 0) {
16758             RemoveInputSource(cps->isr);
16759             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16760                     _(cps->which), cps->program);
16761             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16762             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16763                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16764                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16765                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16766                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16767                 } else {
16768                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16769                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16770                     gameInfo.result = res;
16771                 }
16772                 gameInfo.resultDetails = StrSave(buf);
16773             }
16774             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16775             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16776         } else {
16777             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16778                     _(cps->which), cps->program);
16779             RemoveInputSource(cps->isr);
16780
16781             /* [AS] Program is misbehaving badly... kill it */
16782             if( count == -2 ) {
16783                 DestroyChildProcess( cps->pr, 9 );
16784                 cps->pr = NoProc;
16785             }
16786
16787             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16788         }
16789         return;
16790     }
16791
16792     if ((end_str = strchr(message, '\r')) != NULL)
16793       *end_str = NULLCHAR;
16794     if ((end_str = strchr(message, '\n')) != NULL)
16795       *end_str = NULLCHAR;
16796
16797     if (appData.debugMode) {
16798         TimeMark now; int print = 1;
16799         char *quote = ""; char c; int i;
16800
16801         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16802                 char start = message[0];
16803                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16804                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16805                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16806                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16807                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16808                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16809                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16810                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16811                    sscanf(message, "hint: %c", &c)!=1 &&
16812                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16813                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16814                     print = (appData.engineComments >= 2);
16815                 }
16816                 message[0] = start; // restore original message
16817         }
16818         if(print) {
16819                 GetTimeMark(&now);
16820                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16821                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16822                         quote,
16823                         message);
16824                 if(serverFP)
16825                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16826                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16827                         quote,
16828                         message), fflush(serverFP);
16829         }
16830     }
16831
16832     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16833     if (appData.icsEngineAnalyze) {
16834         if (strstr(message, "whisper") != NULL ||
16835              strstr(message, "kibitz") != NULL ||
16836             strstr(message, "tellics") != NULL) return;
16837     }
16838
16839     HandleMachineMove(message, cps);
16840 }
16841
16842
16843 void
16844 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16845 {
16846     char buf[MSG_SIZ];
16847     int seconds;
16848
16849     if( timeControl_2 > 0 ) {
16850         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16851             tc = timeControl_2;
16852         }
16853     }
16854     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16855     inc /= cps->timeOdds;
16856     st  /= cps->timeOdds;
16857
16858     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16859
16860     if (st > 0) {
16861       /* Set exact time per move, normally using st command */
16862       if (cps->stKludge) {
16863         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16864         seconds = st % 60;
16865         if (seconds == 0) {
16866           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16867         } else {
16868           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16869         }
16870       } else {
16871         snprintf(buf, MSG_SIZ, "st %d\n", st);
16872       }
16873     } else {
16874       /* Set conventional or incremental time control, using level command */
16875       if (seconds == 0) {
16876         /* Note old gnuchess bug -- minutes:seconds used to not work.
16877            Fixed in later versions, but still avoid :seconds
16878            when seconds is 0. */
16879         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16880       } else {
16881         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16882                  seconds, inc/1000.);
16883       }
16884     }
16885     SendToProgram(buf, cps);
16886
16887     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16888     /* Orthogonally, limit search to given depth */
16889     if (sd > 0) {
16890       if (cps->sdKludge) {
16891         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16892       } else {
16893         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16894       }
16895       SendToProgram(buf, cps);
16896     }
16897
16898     if(cps->nps >= 0) { /* [HGM] nps */
16899         if(cps->supportsNPS == FALSE)
16900           cps->nps = -1; // don't use if engine explicitly says not supported!
16901         else {
16902           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16903           SendToProgram(buf, cps);
16904         }
16905     }
16906 }
16907
16908 ChessProgramState *
16909 WhitePlayer ()
16910 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16911 {
16912     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16913        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16914         return &second;
16915     return &first;
16916 }
16917
16918 void
16919 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16920 {
16921     char message[MSG_SIZ];
16922     long time, otime;
16923
16924     /* Note: this routine must be called when the clocks are stopped
16925        or when they have *just* been set or switched; otherwise
16926        it will be off by the time since the current tick started.
16927     */
16928     if (machineWhite) {
16929         time = whiteTimeRemaining / 10;
16930         otime = blackTimeRemaining / 10;
16931     } else {
16932         time = blackTimeRemaining / 10;
16933         otime = whiteTimeRemaining / 10;
16934     }
16935     /* [HGM] translate opponent's time by time-odds factor */
16936     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16937
16938     if (time <= 0) time = 1;
16939     if (otime <= 0) otime = 1;
16940
16941     snprintf(message, MSG_SIZ, "time %ld\n", time);
16942     SendToProgram(message, cps);
16943
16944     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16945     SendToProgram(message, cps);
16946 }
16947
16948 char *
16949 EngineDefinedVariant (ChessProgramState *cps, int n)
16950 {   // return name of n-th unknown variant that engine supports
16951     static char buf[MSG_SIZ];
16952     char *p, *s = cps->variants;
16953     if(!s) return NULL;
16954     do { // parse string from variants feature
16955       VariantClass v;
16956         p = strchr(s, ',');
16957         if(p) *p = NULLCHAR;
16958       v = StringToVariant(s);
16959       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16960         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16961             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16962                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16963                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16964                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16965             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16966         }
16967         if(p) *p++ = ',';
16968         if(n < 0) return buf;
16969     } while(s = p);
16970     return NULL;
16971 }
16972
16973 int
16974 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16975 {
16976   char buf[MSG_SIZ];
16977   int len = strlen(name);
16978   int val;
16979
16980   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16981     (*p) += len + 1;
16982     sscanf(*p, "%d", &val);
16983     *loc = (val != 0);
16984     while (**p && **p != ' ')
16985       (*p)++;
16986     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16987     SendToProgram(buf, cps);
16988     return TRUE;
16989   }
16990   return FALSE;
16991 }
16992
16993 int
16994 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16995 {
16996   char buf[MSG_SIZ];
16997   int len = strlen(name);
16998   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16999     (*p) += len + 1;
17000     sscanf(*p, "%d", loc);
17001     while (**p && **p != ' ') (*p)++;
17002     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17003     SendToProgram(buf, cps);
17004     return TRUE;
17005   }
17006   return FALSE;
17007 }
17008
17009 int
17010 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17011 {
17012   char buf[MSG_SIZ];
17013   int len = strlen(name);
17014   if (strncmp((*p), name, len) == 0
17015       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17016     (*p) += len + 2;
17017     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17018     sscanf(*p, "%[^\"]", *loc);
17019     while (**p && **p != '\"') (*p)++;
17020     if (**p == '\"') (*p)++;
17021     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17022     SendToProgram(buf, cps);
17023     return TRUE;
17024   }
17025   return FALSE;
17026 }
17027
17028 int
17029 ParseOption (Option *opt, ChessProgramState *cps)
17030 // [HGM] options: process the string that defines an engine option, and determine
17031 // name, type, default value, and allowed value range
17032 {
17033         char *p, *q, buf[MSG_SIZ];
17034         int n, min = (-1)<<31, max = 1<<31, def;
17035
17036         opt->target = &opt->value;   // OK for spin/slider and checkbox
17037         if(p = strstr(opt->name, " -spin ")) {
17038             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17039             if(max < min) max = min; // enforce consistency
17040             if(def < min) def = min;
17041             if(def > max) def = max;
17042             opt->value = def;
17043             opt->min = min;
17044             opt->max = max;
17045             opt->type = Spin;
17046         } else if((p = strstr(opt->name, " -slider "))) {
17047             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17048             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17049             if(max < min) max = min; // enforce consistency
17050             if(def < min) def = min;
17051             if(def > max) def = max;
17052             opt->value = def;
17053             opt->min = min;
17054             opt->max = max;
17055             opt->type = Spin; // Slider;
17056         } else if((p = strstr(opt->name, " -string "))) {
17057             opt->textValue = p+9;
17058             opt->type = TextBox;
17059             opt->target = &opt->textValue;
17060         } else if((p = strstr(opt->name, " -file "))) {
17061             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17062             opt->target = opt->textValue = p+7;
17063             opt->type = FileName; // FileName;
17064             opt->target = &opt->textValue;
17065         } else if((p = strstr(opt->name, " -path "))) {
17066             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17067             opt->target = opt->textValue = p+7;
17068             opt->type = PathName; // PathName;
17069             opt->target = &opt->textValue;
17070         } else if(p = strstr(opt->name, " -check ")) {
17071             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17072             opt->value = (def != 0);
17073             opt->type = CheckBox;
17074         } else if(p = strstr(opt->name, " -combo ")) {
17075             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17076             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17077             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17078             opt->value = n = 0;
17079             while(q = StrStr(q, " /// ")) {
17080                 n++; *q = 0;    // count choices, and null-terminate each of them
17081                 q += 5;
17082                 if(*q == '*') { // remember default, which is marked with * prefix
17083                     q++;
17084                     opt->value = n;
17085                 }
17086                 cps->comboList[cps->comboCnt++] = q;
17087             }
17088             cps->comboList[cps->comboCnt++] = NULL;
17089             opt->max = n + 1;
17090             opt->type = ComboBox;
17091         } else if(p = strstr(opt->name, " -button")) {
17092             opt->type = Button;
17093         } else if(p = strstr(opt->name, " -save")) {
17094             opt->type = SaveButton;
17095         } else return FALSE;
17096         *p = 0; // terminate option name
17097         // now look if the command-line options define a setting for this engine option.
17098         if(cps->optionSettings && cps->optionSettings[0])
17099             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17100         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17101           snprintf(buf, MSG_SIZ, "option %s", p);
17102                 if(p = strstr(buf, ",")) *p = 0;
17103                 if(q = strchr(buf, '=')) switch(opt->type) {
17104                     case ComboBox:
17105                         for(n=0; n<opt->max; n++)
17106                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17107                         break;
17108                     case TextBox:
17109                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17110                         break;
17111                     case Spin:
17112                     case CheckBox:
17113                         opt->value = atoi(q+1);
17114                     default:
17115                         break;
17116                 }
17117                 strcat(buf, "\n");
17118                 SendToProgram(buf, cps);
17119         }
17120         return TRUE;
17121 }
17122
17123 void
17124 FeatureDone (ChessProgramState *cps, int val)
17125 {
17126   DelayedEventCallback cb = GetDelayedEvent();
17127   if ((cb == InitBackEnd3 && cps == &first) ||
17128       (cb == SettingsMenuIfReady && cps == &second) ||
17129       (cb == LoadEngine) ||
17130       (cb == TwoMachinesEventIfReady)) {
17131     CancelDelayedEvent();
17132     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17133   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17134   cps->initDone = val;
17135   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17136 }
17137
17138 /* Parse feature command from engine */
17139 void
17140 ParseFeatures (char *args, ChessProgramState *cps)
17141 {
17142   char *p = args;
17143   char *q = NULL;
17144   int val;
17145   char buf[MSG_SIZ];
17146
17147   for (;;) {
17148     while (*p == ' ') p++;
17149     if (*p == NULLCHAR) return;
17150
17151     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17152     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17153     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17154     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17155     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17156     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17157     if (BoolFeature(&p, "reuse", &val, cps)) {
17158       /* Engine can disable reuse, but can't enable it if user said no */
17159       if (!val) cps->reuse = FALSE;
17160       continue;
17161     }
17162     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17163     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17164       if (gameMode == TwoMachinesPlay) {
17165         DisplayTwoMachinesTitle();
17166       } else {
17167         DisplayTitle("");
17168       }
17169       continue;
17170     }
17171     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17172     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17173     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17174     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17175     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17176     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17177     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17178     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17179     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17180     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17181     if (IntFeature(&p, "done", &val, cps)) {
17182       FeatureDone(cps, val);
17183       continue;
17184     }
17185     /* Added by Tord: */
17186     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17187     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17188     /* End of additions by Tord */
17189
17190     /* [HGM] added features: */
17191     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17192     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17193     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17194     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17195     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17196     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17197     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17198     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17199         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17200         FREE(cps->option[cps->nrOptions].name);
17201         cps->option[cps->nrOptions].name = q; q = NULL;
17202         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17203           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17204             SendToProgram(buf, cps);
17205             continue;
17206         }
17207         if(cps->nrOptions >= MAX_OPTIONS) {
17208             cps->nrOptions--;
17209             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17210             DisplayError(buf, 0);
17211         }
17212         continue;
17213     }
17214     /* End of additions by HGM */
17215
17216     /* unknown feature: complain and skip */
17217     q = p;
17218     while (*q && *q != '=') q++;
17219     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17220     SendToProgram(buf, cps);
17221     p = q;
17222     if (*p == '=') {
17223       p++;
17224       if (*p == '\"') {
17225         p++;
17226         while (*p && *p != '\"') p++;
17227         if (*p == '\"') p++;
17228       } else {
17229         while (*p && *p != ' ') p++;
17230       }
17231     }
17232   }
17233
17234 }
17235
17236 void
17237 PeriodicUpdatesEvent (int newState)
17238 {
17239     if (newState == appData.periodicUpdates)
17240       return;
17241
17242     appData.periodicUpdates=newState;
17243
17244     /* Display type changes, so update it now */
17245 //    DisplayAnalysis();
17246
17247     /* Get the ball rolling again... */
17248     if (newState) {
17249         AnalysisPeriodicEvent(1);
17250         StartAnalysisClock();
17251     }
17252 }
17253
17254 void
17255 PonderNextMoveEvent (int newState)
17256 {
17257     if (newState == appData.ponderNextMove) return;
17258     if (gameMode == EditPosition) EditPositionDone(TRUE);
17259     if (newState) {
17260         SendToProgram("hard\n", &first);
17261         if (gameMode == TwoMachinesPlay) {
17262             SendToProgram("hard\n", &second);
17263         }
17264     } else {
17265         SendToProgram("easy\n", &first);
17266         thinkOutput[0] = NULLCHAR;
17267         if (gameMode == TwoMachinesPlay) {
17268             SendToProgram("easy\n", &second);
17269         }
17270     }
17271     appData.ponderNextMove = newState;
17272 }
17273
17274 void
17275 NewSettingEvent (int option, int *feature, char *command, int value)
17276 {
17277     char buf[MSG_SIZ];
17278
17279     if (gameMode == EditPosition) EditPositionDone(TRUE);
17280     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17281     if(feature == NULL || *feature) SendToProgram(buf, &first);
17282     if (gameMode == TwoMachinesPlay) {
17283         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17284     }
17285 }
17286
17287 void
17288 ShowThinkingEvent ()
17289 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17290 {
17291     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17292     int newState = appData.showThinking
17293         // [HGM] thinking: other features now need thinking output as well
17294         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17295
17296     if (oldState == newState) return;
17297     oldState = newState;
17298     if (gameMode == EditPosition) EditPositionDone(TRUE);
17299     if (oldState) {
17300         SendToProgram("post\n", &first);
17301         if (gameMode == TwoMachinesPlay) {
17302             SendToProgram("post\n", &second);
17303         }
17304     } else {
17305         SendToProgram("nopost\n", &first);
17306         thinkOutput[0] = NULLCHAR;
17307         if (gameMode == TwoMachinesPlay) {
17308             SendToProgram("nopost\n", &second);
17309         }
17310     }
17311 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17312 }
17313
17314 void
17315 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17316 {
17317   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17318   if (pr == NoProc) return;
17319   AskQuestion(title, question, replyPrefix, pr);
17320 }
17321
17322 void
17323 TypeInEvent (char firstChar)
17324 {
17325     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17326         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17327         gameMode == AnalyzeMode || gameMode == EditGame ||
17328         gameMode == EditPosition || gameMode == IcsExamining ||
17329         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17330         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17331                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17332                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17333         gameMode == Training) PopUpMoveDialog(firstChar);
17334 }
17335
17336 void
17337 TypeInDoneEvent (char *move)
17338 {
17339         Board board;
17340         int n, fromX, fromY, toX, toY;
17341         char promoChar;
17342         ChessMove moveType;
17343
17344         // [HGM] FENedit
17345         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17346                 EditPositionPasteFEN(move);
17347                 return;
17348         }
17349         // [HGM] movenum: allow move number to be typed in any mode
17350         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17351           ToNrEvent(2*n-1);
17352           return;
17353         }
17354         // undocumented kludge: allow command-line option to be typed in!
17355         // (potentially fatal, and does not implement the effect of the option.)
17356         // should only be used for options that are values on which future decisions will be made,
17357         // and definitely not on options that would be used during initialization.
17358         if(strstr(move, "!!! -") == move) {
17359             ParseArgsFromString(move+4);
17360             return;
17361         }
17362
17363       if (gameMode != EditGame && currentMove != forwardMostMove &&
17364         gameMode != Training) {
17365         DisplayMoveError(_("Displayed move is not current"));
17366       } else {
17367         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17368           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17369         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17370         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17371           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17372           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17373         } else {
17374           DisplayMoveError(_("Could not parse move"));
17375         }
17376       }
17377 }
17378
17379 void
17380 DisplayMove (int moveNumber)
17381 {
17382     char message[MSG_SIZ];
17383     char res[MSG_SIZ];
17384     char cpThinkOutput[MSG_SIZ];
17385
17386     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17387
17388     if (moveNumber == forwardMostMove - 1 ||
17389         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17390
17391         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17392
17393         if (strchr(cpThinkOutput, '\n')) {
17394             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17395         }
17396     } else {
17397         *cpThinkOutput = NULLCHAR;
17398     }
17399
17400     /* [AS] Hide thinking from human user */
17401     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17402         *cpThinkOutput = NULLCHAR;
17403         if( thinkOutput[0] != NULLCHAR ) {
17404             int i;
17405
17406             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17407                 cpThinkOutput[i] = '.';
17408             }
17409             cpThinkOutput[i] = NULLCHAR;
17410             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17411         }
17412     }
17413
17414     if (moveNumber == forwardMostMove - 1 &&
17415         gameInfo.resultDetails != NULL) {
17416         if (gameInfo.resultDetails[0] == NULLCHAR) {
17417           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17418         } else {
17419           snprintf(res, MSG_SIZ, " {%s} %s",
17420                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17421         }
17422     } else {
17423         res[0] = NULLCHAR;
17424     }
17425
17426     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17427         DisplayMessage(res, cpThinkOutput);
17428     } else {
17429       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17430                 WhiteOnMove(moveNumber) ? " " : ".. ",
17431                 parseList[moveNumber], res);
17432         DisplayMessage(message, cpThinkOutput);
17433     }
17434 }
17435
17436 void
17437 DisplayComment (int moveNumber, char *text)
17438 {
17439     char title[MSG_SIZ];
17440
17441     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17442       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17443     } else {
17444       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17445               WhiteOnMove(moveNumber) ? " " : ".. ",
17446               parseList[moveNumber]);
17447     }
17448     if (text != NULL && (appData.autoDisplayComment || commentUp))
17449         CommentPopUp(title, text);
17450 }
17451
17452 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17453  * might be busy thinking or pondering.  It can be omitted if your
17454  * gnuchess is configured to stop thinking immediately on any user
17455  * input.  However, that gnuchess feature depends on the FIONREAD
17456  * ioctl, which does not work properly on some flavors of Unix.
17457  */
17458 void
17459 Attention (ChessProgramState *cps)
17460 {
17461 #if ATTENTION
17462     if (!cps->useSigint) return;
17463     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17464     switch (gameMode) {
17465       case MachinePlaysWhite:
17466       case MachinePlaysBlack:
17467       case TwoMachinesPlay:
17468       case IcsPlayingWhite:
17469       case IcsPlayingBlack:
17470       case AnalyzeMode:
17471       case AnalyzeFile:
17472         /* Skip if we know it isn't thinking */
17473         if (!cps->maybeThinking) return;
17474         if (appData.debugMode)
17475           fprintf(debugFP, "Interrupting %s\n", cps->which);
17476         InterruptChildProcess(cps->pr);
17477         cps->maybeThinking = FALSE;
17478         break;
17479       default:
17480         break;
17481     }
17482 #endif /*ATTENTION*/
17483 }
17484
17485 int
17486 CheckFlags ()
17487 {
17488     if (whiteTimeRemaining <= 0) {
17489         if (!whiteFlag) {
17490             whiteFlag = TRUE;
17491             if (appData.icsActive) {
17492                 if (appData.autoCallFlag &&
17493                     gameMode == IcsPlayingBlack && !blackFlag) {
17494                   SendToICS(ics_prefix);
17495                   SendToICS("flag\n");
17496                 }
17497             } else {
17498                 if (blackFlag) {
17499                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17500                 } else {
17501                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17502                     if (appData.autoCallFlag) {
17503                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17504                         return TRUE;
17505                     }
17506                 }
17507             }
17508         }
17509     }
17510     if (blackTimeRemaining <= 0) {
17511         if (!blackFlag) {
17512             blackFlag = TRUE;
17513             if (appData.icsActive) {
17514                 if (appData.autoCallFlag &&
17515                     gameMode == IcsPlayingWhite && !whiteFlag) {
17516                   SendToICS(ics_prefix);
17517                   SendToICS("flag\n");
17518                 }
17519             } else {
17520                 if (whiteFlag) {
17521                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17522                 } else {
17523                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17524                     if (appData.autoCallFlag) {
17525                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17526                         return TRUE;
17527                     }
17528                 }
17529             }
17530         }
17531     }
17532     return FALSE;
17533 }
17534
17535 void
17536 CheckTimeControl ()
17537 {
17538     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17539         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17540
17541     /*
17542      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17543      */
17544     if ( !WhiteOnMove(forwardMostMove) ) {
17545         /* White made time control */
17546         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17547         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17548         /* [HGM] time odds: correct new time quota for time odds! */
17549                                             / WhitePlayer()->timeOdds;
17550         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17551     } else {
17552         lastBlack -= blackTimeRemaining;
17553         /* Black made time control */
17554         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17555                                             / WhitePlayer()->other->timeOdds;
17556         lastWhite = whiteTimeRemaining;
17557     }
17558 }
17559
17560 void
17561 DisplayBothClocks ()
17562 {
17563     int wom = gameMode == EditPosition ?
17564       !blackPlaysFirst : WhiteOnMove(currentMove);
17565     DisplayWhiteClock(whiteTimeRemaining, wom);
17566     DisplayBlackClock(blackTimeRemaining, !wom);
17567 }
17568
17569
17570 /* Timekeeping seems to be a portability nightmare.  I think everyone
17571    has ftime(), but I'm really not sure, so I'm including some ifdefs
17572    to use other calls if you don't.  Clocks will be less accurate if
17573    you have neither ftime nor gettimeofday.
17574 */
17575
17576 /* VS 2008 requires the #include outside of the function */
17577 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17578 #include <sys/timeb.h>
17579 #endif
17580
17581 /* Get the current time as a TimeMark */
17582 void
17583 GetTimeMark (TimeMark *tm)
17584 {
17585 #if HAVE_GETTIMEOFDAY
17586
17587     struct timeval timeVal;
17588     struct timezone timeZone;
17589
17590     gettimeofday(&timeVal, &timeZone);
17591     tm->sec = (long) timeVal.tv_sec;
17592     tm->ms = (int) (timeVal.tv_usec / 1000L);
17593
17594 #else /*!HAVE_GETTIMEOFDAY*/
17595 #if HAVE_FTIME
17596
17597 // include <sys/timeb.h> / moved to just above start of function
17598     struct timeb timeB;
17599
17600     ftime(&timeB);
17601     tm->sec = (long) timeB.time;
17602     tm->ms = (int) timeB.millitm;
17603
17604 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17605     tm->sec = (long) time(NULL);
17606     tm->ms = 0;
17607 #endif
17608 #endif
17609 }
17610
17611 /* Return the difference in milliseconds between two
17612    time marks.  We assume the difference will fit in a long!
17613 */
17614 long
17615 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17616 {
17617     return 1000L*(tm2->sec - tm1->sec) +
17618            (long) (tm2->ms - tm1->ms);
17619 }
17620
17621
17622 /*
17623  * Code to manage the game clocks.
17624  *
17625  * In tournament play, black starts the clock and then white makes a move.
17626  * We give the human user a slight advantage if he is playing white---the
17627  * clocks don't run until he makes his first move, so it takes zero time.
17628  * Also, we don't account for network lag, so we could get out of sync
17629  * with GNU Chess's clock -- but then, referees are always right.
17630  */
17631
17632 static TimeMark tickStartTM;
17633 static long intendedTickLength;
17634
17635 long
17636 NextTickLength (long timeRemaining)
17637 {
17638     long nominalTickLength, nextTickLength;
17639
17640     if (timeRemaining > 0L && timeRemaining <= 10000L)
17641       nominalTickLength = 100L;
17642     else
17643       nominalTickLength = 1000L;
17644     nextTickLength = timeRemaining % nominalTickLength;
17645     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17646
17647     return nextTickLength;
17648 }
17649
17650 /* Adjust clock one minute up or down */
17651 void
17652 AdjustClock (Boolean which, int dir)
17653 {
17654     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17655     if(which) blackTimeRemaining += 60000*dir;
17656     else      whiteTimeRemaining += 60000*dir;
17657     DisplayBothClocks();
17658     adjustedClock = TRUE;
17659 }
17660
17661 /* Stop clocks and reset to a fresh time control */
17662 void
17663 ResetClocks ()
17664 {
17665     (void) StopClockTimer();
17666     if (appData.icsActive) {
17667         whiteTimeRemaining = blackTimeRemaining = 0;
17668     } else if (searchTime) {
17669         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17670         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17671     } else { /* [HGM] correct new time quote for time odds */
17672         whiteTC = blackTC = fullTimeControlString;
17673         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17674         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17675     }
17676     if (whiteFlag || blackFlag) {
17677         DisplayTitle("");
17678         whiteFlag = blackFlag = FALSE;
17679     }
17680     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17681     DisplayBothClocks();
17682     adjustedClock = FALSE;
17683 }
17684
17685 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17686
17687 /* Decrement running clock by amount of time that has passed */
17688 void
17689 DecrementClocks ()
17690 {
17691     long timeRemaining;
17692     long lastTickLength, fudge;
17693     TimeMark now;
17694
17695     if (!appData.clockMode) return;
17696     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17697
17698     GetTimeMark(&now);
17699
17700     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17701
17702     /* Fudge if we woke up a little too soon */
17703     fudge = intendedTickLength - lastTickLength;
17704     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17705
17706     if (WhiteOnMove(forwardMostMove)) {
17707         if(whiteNPS >= 0) lastTickLength = 0;
17708         timeRemaining = whiteTimeRemaining -= lastTickLength;
17709         if(timeRemaining < 0 && !appData.icsActive) {
17710             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17711             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17712                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17713                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17714             }
17715         }
17716         DisplayWhiteClock(whiteTimeRemaining - fudge,
17717                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17718     } else {
17719         if(blackNPS >= 0) lastTickLength = 0;
17720         timeRemaining = blackTimeRemaining -= lastTickLength;
17721         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17722             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17723             if(suddenDeath) {
17724                 blackStartMove = forwardMostMove;
17725                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17726             }
17727         }
17728         DisplayBlackClock(blackTimeRemaining - fudge,
17729                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17730     }
17731     if (CheckFlags()) return;
17732
17733     if(twoBoards) { // count down secondary board's clocks as well
17734         activePartnerTime -= lastTickLength;
17735         partnerUp = 1;
17736         if(activePartner == 'W')
17737             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17738         else
17739             DisplayBlackClock(activePartnerTime, TRUE);
17740         partnerUp = 0;
17741     }
17742
17743     tickStartTM = now;
17744     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17745     StartClockTimer(intendedTickLength);
17746
17747     /* if the time remaining has fallen below the alarm threshold, sound the
17748      * alarm. if the alarm has sounded and (due to a takeback or time control
17749      * with increment) the time remaining has increased to a level above the
17750      * threshold, reset the alarm so it can sound again.
17751      */
17752
17753     if (appData.icsActive && appData.icsAlarm) {
17754
17755         /* make sure we are dealing with the user's clock */
17756         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17757                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17758            )) return;
17759
17760         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17761             alarmSounded = FALSE;
17762         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17763             PlayAlarmSound();
17764             alarmSounded = TRUE;
17765         }
17766     }
17767 }
17768
17769
17770 /* A player has just moved, so stop the previously running
17771    clock and (if in clock mode) start the other one.
17772    We redisplay both clocks in case we're in ICS mode, because
17773    ICS gives us an update to both clocks after every move.
17774    Note that this routine is called *after* forwardMostMove
17775    is updated, so the last fractional tick must be subtracted
17776    from the color that is *not* on move now.
17777 */
17778 void
17779 SwitchClocks (int newMoveNr)
17780 {
17781     long lastTickLength;
17782     TimeMark now;
17783     int flagged = FALSE;
17784
17785     GetTimeMark(&now);
17786
17787     if (StopClockTimer() && appData.clockMode) {
17788         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17789         if (!WhiteOnMove(forwardMostMove)) {
17790             if(blackNPS >= 0) lastTickLength = 0;
17791             blackTimeRemaining -= lastTickLength;
17792            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17793 //         if(pvInfoList[forwardMostMove].time == -1)
17794                  pvInfoList[forwardMostMove].time =               // use GUI time
17795                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17796         } else {
17797            if(whiteNPS >= 0) lastTickLength = 0;
17798            whiteTimeRemaining -= lastTickLength;
17799            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17800 //         if(pvInfoList[forwardMostMove].time == -1)
17801                  pvInfoList[forwardMostMove].time =
17802                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17803         }
17804         flagged = CheckFlags();
17805     }
17806     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17807     CheckTimeControl();
17808
17809     if (flagged || !appData.clockMode) return;
17810
17811     switch (gameMode) {
17812       case MachinePlaysBlack:
17813       case MachinePlaysWhite:
17814       case BeginningOfGame:
17815         if (pausing) return;
17816         break;
17817
17818       case EditGame:
17819       case PlayFromGameFile:
17820       case IcsExamining:
17821         return;
17822
17823       default:
17824         break;
17825     }
17826
17827     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17828         if(WhiteOnMove(forwardMostMove))
17829              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17830         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17831     }
17832
17833     tickStartTM = now;
17834     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17835       whiteTimeRemaining : blackTimeRemaining);
17836     StartClockTimer(intendedTickLength);
17837 }
17838
17839
17840 /* Stop both clocks */
17841 void
17842 StopClocks ()
17843 {
17844     long lastTickLength;
17845     TimeMark now;
17846
17847     if (!StopClockTimer()) return;
17848     if (!appData.clockMode) return;
17849
17850     GetTimeMark(&now);
17851
17852     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17853     if (WhiteOnMove(forwardMostMove)) {
17854         if(whiteNPS >= 0) lastTickLength = 0;
17855         whiteTimeRemaining -= lastTickLength;
17856         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17857     } else {
17858         if(blackNPS >= 0) lastTickLength = 0;
17859         blackTimeRemaining -= lastTickLength;
17860         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17861     }
17862     CheckFlags();
17863 }
17864
17865 /* Start clock of player on move.  Time may have been reset, so
17866    if clock is already running, stop and restart it. */
17867 void
17868 StartClocks ()
17869 {
17870     (void) StopClockTimer(); /* in case it was running already */
17871     DisplayBothClocks();
17872     if (CheckFlags()) return;
17873
17874     if (!appData.clockMode) return;
17875     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17876
17877     GetTimeMark(&tickStartTM);
17878     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17879       whiteTimeRemaining : blackTimeRemaining);
17880
17881    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17882     whiteNPS = blackNPS = -1;
17883     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17884        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17885         whiteNPS = first.nps;
17886     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17887        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17888         blackNPS = first.nps;
17889     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17890         whiteNPS = second.nps;
17891     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17892         blackNPS = second.nps;
17893     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17894
17895     StartClockTimer(intendedTickLength);
17896 }
17897
17898 char *
17899 TimeString (long ms)
17900 {
17901     long second, minute, hour, day;
17902     char *sign = "";
17903     static char buf[32];
17904
17905     if (ms > 0 && ms <= 9900) {
17906       /* convert milliseconds to tenths, rounding up */
17907       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17908
17909       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17910       return buf;
17911     }
17912
17913     /* convert milliseconds to seconds, rounding up */
17914     /* use floating point to avoid strangeness of integer division
17915        with negative dividends on many machines */
17916     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17917
17918     if (second < 0) {
17919         sign = "-";
17920         second = -second;
17921     }
17922
17923     day = second / (60 * 60 * 24);
17924     second = second % (60 * 60 * 24);
17925     hour = second / (60 * 60);
17926     second = second % (60 * 60);
17927     minute = second / 60;
17928     second = second % 60;
17929
17930     if (day > 0)
17931       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17932               sign, day, hour, minute, second);
17933     else if (hour > 0)
17934       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17935     else
17936       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17937
17938     return buf;
17939 }
17940
17941
17942 /*
17943  * This is necessary because some C libraries aren't ANSI C compliant yet.
17944  */
17945 char *
17946 StrStr (char *string, char *match)
17947 {
17948     int i, length;
17949
17950     length = strlen(match);
17951
17952     for (i = strlen(string) - length; i >= 0; i--, string++)
17953       if (!strncmp(match, string, length))
17954         return string;
17955
17956     return NULL;
17957 }
17958
17959 char *
17960 StrCaseStr (char *string, char *match)
17961 {
17962     int i, j, length;
17963
17964     length = strlen(match);
17965
17966     for (i = strlen(string) - length; i >= 0; i--, string++) {
17967         for (j = 0; j < length; j++) {
17968             if (ToLower(match[j]) != ToLower(string[j]))
17969               break;
17970         }
17971         if (j == length) return string;
17972     }
17973
17974     return NULL;
17975 }
17976
17977 #ifndef _amigados
17978 int
17979 StrCaseCmp (char *s1, char *s2)
17980 {
17981     char c1, c2;
17982
17983     for (;;) {
17984         c1 = ToLower(*s1++);
17985         c2 = ToLower(*s2++);
17986         if (c1 > c2) return 1;
17987         if (c1 < c2) return -1;
17988         if (c1 == NULLCHAR) return 0;
17989     }
17990 }
17991
17992
17993 int
17994 ToLower (int c)
17995 {
17996     return isupper(c) ? tolower(c) : c;
17997 }
17998
17999
18000 int
18001 ToUpper (int c)
18002 {
18003     return islower(c) ? toupper(c) : c;
18004 }
18005 #endif /* !_amigados    */
18006
18007 char *
18008 StrSave (char *s)
18009 {
18010   char *ret;
18011
18012   if ((ret = (char *) malloc(strlen(s) + 1)))
18013     {
18014       safeStrCpy(ret, s, strlen(s)+1);
18015     }
18016   return ret;
18017 }
18018
18019 char *
18020 StrSavePtr (char *s, char **savePtr)
18021 {
18022     if (*savePtr) {
18023         free(*savePtr);
18024     }
18025     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18026       safeStrCpy(*savePtr, s, strlen(s)+1);
18027     }
18028     return(*savePtr);
18029 }
18030
18031 char *
18032 PGNDate ()
18033 {
18034     time_t clock;
18035     struct tm *tm;
18036     char buf[MSG_SIZ];
18037
18038     clock = time((time_t *)NULL);
18039     tm = localtime(&clock);
18040     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18041             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18042     return StrSave(buf);
18043 }
18044
18045
18046 char *
18047 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18048 {
18049     int i, j, fromX, fromY, toX, toY;
18050     int whiteToPlay, haveRights = nrCastlingRights;
18051     char buf[MSG_SIZ];
18052     char *p, *q;
18053     int emptycount;
18054     ChessSquare piece;
18055
18056     whiteToPlay = (gameMode == EditPosition) ?
18057       !blackPlaysFirst : (move % 2 == 0);
18058     p = buf;
18059
18060     /* Piece placement data */
18061     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18062         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18063         emptycount = 0;
18064         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18065             if (boards[move][i][j] == EmptySquare) {
18066                 emptycount++;
18067             } else { ChessSquare piece = boards[move][i][j];
18068                 if (emptycount > 0) {
18069                     if(emptycount<10) /* [HGM] can be >= 10 */
18070                         *p++ = '0' + emptycount;
18071                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18072                     emptycount = 0;
18073                 }
18074                 if(PieceToChar(piece) == '+') {
18075                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18076                     *p++ = '+';
18077                     piece = (ChessSquare)(CHUDEMOTED(piece));
18078                 }
18079                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18080                 if(*p = PieceSuffix(piece)) p++;
18081                 if(p[-1] == '~') {
18082                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18083                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18084                     *p++ = '~';
18085                 }
18086             }
18087         }
18088         if (emptycount > 0) {
18089             if(emptycount<10) /* [HGM] can be >= 10 */
18090                 *p++ = '0' + emptycount;
18091             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18092             emptycount = 0;
18093         }
18094         *p++ = '/';
18095     }
18096     *(p - 1) = ' ';
18097
18098     /* [HGM] print Crazyhouse or Shogi holdings */
18099     if( gameInfo.holdingsWidth ) {
18100         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18101         q = p;
18102         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18103             piece = boards[move][i][BOARD_WIDTH-1];
18104             if( piece != EmptySquare )
18105               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18106                   *p++ = PieceToChar(piece);
18107         }
18108         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18109             piece = boards[move][BOARD_HEIGHT-i-1][0];
18110             if( piece != EmptySquare )
18111               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18112                   *p++ = PieceToChar(piece);
18113         }
18114
18115         if( q == p ) *p++ = '-';
18116         *p++ = ']';
18117         *p++ = ' ';
18118     }
18119
18120     /* Active color */
18121     *p++ = whiteToPlay ? 'w' : 'b';
18122     *p++ = ' ';
18123
18124   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18125     haveRights = 0; q = p;
18126     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18127       piece = boards[move][0][i];
18128       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18129         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18130       }
18131     }
18132     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18133       piece = boards[move][BOARD_HEIGHT-1][i];
18134       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18135         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18136       }
18137     }
18138     if(p == q) *p++ = '-';
18139     *p++ = ' ';
18140   }
18141
18142   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18143     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18144   } else {
18145   if(haveRights) {
18146      int handW=0, handB=0;
18147      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18148         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18149         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18150      }
18151      q = p;
18152      if(appData.fischerCastling) {
18153         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18154            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18155                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18156         } else {
18157        /* [HGM] write directly from rights */
18158            if(boards[move][CASTLING][2] != NoRights &&
18159               boards[move][CASTLING][0] != NoRights   )
18160                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18161            if(boards[move][CASTLING][2] != NoRights &&
18162               boards[move][CASTLING][1] != NoRights   )
18163                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18164         }
18165         if(handB) {
18166            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18167                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18168         } else {
18169            if(boards[move][CASTLING][5] != NoRights &&
18170               boards[move][CASTLING][3] != NoRights   )
18171                 *p++ = boards[move][CASTLING][3] + AAA;
18172            if(boards[move][CASTLING][5] != NoRights &&
18173               boards[move][CASTLING][4] != NoRights   )
18174                 *p++ = boards[move][CASTLING][4] + AAA;
18175         }
18176      } else {
18177
18178         /* [HGM] write true castling rights */
18179         if( nrCastlingRights == 6 ) {
18180             int q, k=0;
18181             if(boards[move][CASTLING][0] != NoRights &&
18182                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18183             q = (boards[move][CASTLING][1] != NoRights &&
18184                  boards[move][CASTLING][2] != NoRights  );
18185             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18186                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18187                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18188                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18189             }
18190             if(q) *p++ = 'Q';
18191             k = 0;
18192             if(boards[move][CASTLING][3] != NoRights &&
18193                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18194             q = (boards[move][CASTLING][4] != NoRights &&
18195                  boards[move][CASTLING][5] != NoRights  );
18196             if(handB) {
18197                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18198                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18199                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18200             }
18201             if(q) *p++ = 'q';
18202         }
18203      }
18204      if (q == p) *p++ = '-'; /* No castling rights */
18205      *p++ = ' ';
18206   }
18207
18208   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18209      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18210      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18211     /* En passant target square */
18212     if (move > backwardMostMove) {
18213         fromX = moveList[move - 1][0] - AAA;
18214         fromY = moveList[move - 1][1] - ONE;
18215         toX = moveList[move - 1][2] - AAA;
18216         toY = moveList[move - 1][3] - ONE;
18217         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18218             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18219             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18220             fromX == toX) {
18221             /* 2-square pawn move just happened */
18222             *p++ = toX + AAA;
18223             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18224         } else {
18225             *p++ = '-';
18226         }
18227     } else if(move == backwardMostMove) {
18228         // [HGM] perhaps we should always do it like this, and forget the above?
18229         if((signed char)boards[move][EP_STATUS] >= 0) {
18230             *p++ = boards[move][EP_STATUS] + AAA;
18231             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18232         } else {
18233             *p++ = '-';
18234         }
18235     } else {
18236         *p++ = '-';
18237     }
18238     *p++ = ' ';
18239   }
18240   }
18241
18242     if(moveCounts)
18243     {   int i = 0, j=move;
18244
18245         /* [HGM] find reversible plies */
18246         if (appData.debugMode) { int k;
18247             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18248             for(k=backwardMostMove; k<=forwardMostMove; k++)
18249                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18250
18251         }
18252
18253         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18254         if( j == backwardMostMove ) i += initialRulePlies;
18255         sprintf(p, "%d ", i);
18256         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18257
18258         /* Fullmove number */
18259         sprintf(p, "%d", (move / 2) + 1);
18260     } else *--p = NULLCHAR;
18261
18262     return StrSave(buf);
18263 }
18264
18265 Boolean
18266 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18267 {
18268     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18269     char *p, c;
18270     int emptycount, virgin[BOARD_FILES];
18271     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18272
18273     p = fen;
18274
18275     /* Piece placement data */
18276     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18277         j = 0;
18278         for (;;) {
18279             if (*p == '/' || *p == ' ' || *p == '[' ) {
18280                 if(j > w) w = j;
18281                 emptycount = gameInfo.boardWidth - j;
18282                 while (emptycount--)
18283                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18284                 if (*p == '/') p++;
18285                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18286                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18287                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18288                     }
18289                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18290                 }
18291                 break;
18292 #if(BOARD_FILES >= 10)*0
18293             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18294                 p++; emptycount=10;
18295                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18296                 while (emptycount--)
18297                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18298 #endif
18299             } else if (*p == '*') {
18300                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18301             } else if (isdigit(*p)) {
18302                 emptycount = *p++ - '0';
18303                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18304                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18305                 while (emptycount--)
18306                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18307             } else if (*p == '<') {
18308                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18309                 else if (i != 0 || !shuffle) return FALSE;
18310                 p++;
18311             } else if (shuffle && *p == '>') {
18312                 p++; // for now ignore closing shuffle range, and assume rank-end
18313             } else if (*p == '?') {
18314                 if (j >= gameInfo.boardWidth) return FALSE;
18315                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18316                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18317             } else if (*p == '+' || isalpha(*p)) {
18318                 char *q, *s = SUFFIXES;
18319                 if (j >= gameInfo.boardWidth) return FALSE;
18320                 if(*p=='+') {
18321                     char c = *++p;
18322                     if(q = strchr(s, p[1])) p++;
18323                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18324                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18325                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18326                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18327                 } else {
18328                     char c = *p++;
18329                     if(q = strchr(s, *p)) p++;
18330                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18331                 }
18332
18333                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18334                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18335                     piece = (ChessSquare) (PROMOTED(piece));
18336                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18337                     p++;
18338                 }
18339                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18340                 if(piece == king) wKingRank = i;
18341                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18342             } else {
18343                 return FALSE;
18344             }
18345         }
18346     }
18347     while (*p == '/' || *p == ' ') p++;
18348
18349     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18350
18351     /* [HGM] by default clear Crazyhouse holdings, if present */
18352     if(gameInfo.holdingsWidth) {
18353        for(i=0; i<BOARD_HEIGHT; i++) {
18354            board[i][0]             = EmptySquare; /* black holdings */
18355            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18356            board[i][1]             = (ChessSquare) 0; /* black counts */
18357            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18358        }
18359     }
18360
18361     /* [HGM] look for Crazyhouse holdings here */
18362     while(*p==' ') p++;
18363     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18364         int swap=0, wcnt=0, bcnt=0;
18365         if(*p == '[') p++;
18366         if(*p == '<') swap++, p++;
18367         if(*p == '-' ) p++; /* empty holdings */ else {
18368             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18369             /* if we would allow FEN reading to set board size, we would   */
18370             /* have to add holdings and shift the board read so far here   */
18371             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18372                 p++;
18373                 if((int) piece >= (int) BlackPawn ) {
18374                     i = (int)piece - (int)BlackPawn;
18375                     i = PieceToNumber((ChessSquare)i);
18376                     if( i >= gameInfo.holdingsSize ) return FALSE;
18377                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18378                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18379                     bcnt++;
18380                 } else {
18381                     i = (int)piece - (int)WhitePawn;
18382                     i = PieceToNumber((ChessSquare)i);
18383                     if( i >= gameInfo.holdingsSize ) return FALSE;
18384                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18385                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18386                     wcnt++;
18387                 }
18388             }
18389             if(subst) { // substitute back-rank question marks by holdings pieces
18390                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18391                     int k, m, n = bcnt + 1;
18392                     if(board[0][j] == ClearBoard) {
18393                         if(!wcnt) return FALSE;
18394                         n = rand() % wcnt;
18395                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18396                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18397                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18398                             break;
18399                         }
18400                     }
18401                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18402                         if(!bcnt) return FALSE;
18403                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18404                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18405                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18406                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18407                             break;
18408                         }
18409                     }
18410                 }
18411                 subst = 0;
18412             }
18413         }
18414         if(*p == ']') p++;
18415     }
18416
18417     if(subst) return FALSE; // substitution requested, but no holdings
18418
18419     while(*p == ' ') p++;
18420
18421     /* Active color */
18422     c = *p++;
18423     if(appData.colorNickNames) {
18424       if( c == appData.colorNickNames[0] ) c = 'w'; else
18425       if( c == appData.colorNickNames[1] ) c = 'b';
18426     }
18427     switch (c) {
18428       case 'w':
18429         *blackPlaysFirst = FALSE;
18430         break;
18431       case 'b':
18432         *blackPlaysFirst = TRUE;
18433         break;
18434       default:
18435         return FALSE;
18436     }
18437
18438     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18439     /* return the extra info in global variiables             */
18440
18441     while(*p==' ') p++;
18442
18443     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18444         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18445         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18446     }
18447
18448     /* set defaults in case FEN is incomplete */
18449     board[EP_STATUS] = EP_UNKNOWN;
18450     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18451     for(i=0; i<nrCastlingRights; i++ ) {
18452         board[CASTLING][i] =
18453             appData.fischerCastling ? NoRights : initialRights[i];
18454     }   /* assume possible unless obviously impossible */
18455     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18456     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18457     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18458                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18459     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18460     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18461     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18462                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18463     FENrulePlies = 0;
18464
18465     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18466       char *q = p;
18467       int w=0, b=0;
18468       while(isalpha(*p)) {
18469         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18470         if(islower(*p)) b |= 1 << (*p++ - 'a');
18471       }
18472       if(*p == '-') p++;
18473       if(p != q) {
18474         board[TOUCHED_W] = ~w;
18475         board[TOUCHED_B] = ~b;
18476         while(*p == ' ') p++;
18477       }
18478     } else
18479
18480     if(nrCastlingRights) {
18481       int fischer = 0;
18482       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18483       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18484           /* castling indicator present, so default becomes no castlings */
18485           for(i=0; i<nrCastlingRights; i++ ) {
18486                  board[CASTLING][i] = NoRights;
18487           }
18488       }
18489       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18490              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18491              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18492              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18493         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18494
18495         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18496             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18497             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18498         }
18499         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18500             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18501         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18502                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18503         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18504                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18505         switch(c) {
18506           case'K':
18507               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18508               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18509               board[CASTLING][2] = whiteKingFile;
18510               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18511               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18512               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18513               break;
18514           case'Q':
18515               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18516               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18517               board[CASTLING][2] = whiteKingFile;
18518               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18519               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18520               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18521               break;
18522           case'k':
18523               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18524               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18525               board[CASTLING][5] = blackKingFile;
18526               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18527               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18528               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18529               break;
18530           case'q':
18531               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18532               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18533               board[CASTLING][5] = blackKingFile;
18534               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18535               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18536               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18537           case '-':
18538               break;
18539           default: /* FRC castlings */
18540               if(c >= 'a') { /* black rights */
18541                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18542                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18543                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18544                   if(i == BOARD_RGHT) break;
18545                   board[CASTLING][5] = i;
18546                   c -= AAA;
18547                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18548                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18549                   if(c > i)
18550                       board[CASTLING][3] = c;
18551                   else
18552                       board[CASTLING][4] = c;
18553               } else { /* white rights */
18554                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18555                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18556                     if(board[0][i] == WhiteKing) break;
18557                   if(i == BOARD_RGHT) break;
18558                   board[CASTLING][2] = i;
18559                   c -= AAA - 'a' + 'A';
18560                   if(board[0][c] >= WhiteKing) break;
18561                   if(c > i)
18562                       board[CASTLING][0] = c;
18563                   else
18564                       board[CASTLING][1] = c;
18565               }
18566         }
18567       }
18568       for(i=0; i<nrCastlingRights; i++)
18569         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18570       if(gameInfo.variant == VariantSChess)
18571         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18572       if(fischer && shuffle) appData.fischerCastling = TRUE;
18573     if (appData.debugMode) {
18574         fprintf(debugFP, "FEN castling rights:");
18575         for(i=0; i<nrCastlingRights; i++)
18576         fprintf(debugFP, " %d", board[CASTLING][i]);
18577         fprintf(debugFP, "\n");
18578     }
18579
18580       while(*p==' ') p++;
18581     }
18582
18583     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18584
18585     /* read e.p. field in games that know e.p. capture */
18586     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18587        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18588        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18589       if(*p=='-') {
18590         p++; board[EP_STATUS] = EP_NONE;
18591       } else {
18592          char c = *p++ - AAA;
18593
18594          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18595          if(*p >= '0' && *p <='9') p++;
18596          board[EP_STATUS] = c;
18597       }
18598     }
18599
18600
18601     if(sscanf(p, "%d", &i) == 1) {
18602         FENrulePlies = i; /* 50-move ply counter */
18603         /* (The move number is still ignored)    */
18604     }
18605
18606     return TRUE;
18607 }
18608
18609 void
18610 EditPositionPasteFEN (char *fen)
18611 {
18612   if (fen != NULL) {
18613     Board initial_position;
18614
18615     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18616       DisplayError(_("Bad FEN position in clipboard"), 0);
18617       return ;
18618     } else {
18619       int savedBlackPlaysFirst = blackPlaysFirst;
18620       EditPositionEvent();
18621       blackPlaysFirst = savedBlackPlaysFirst;
18622       CopyBoard(boards[0], initial_position);
18623       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18624       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18625       DisplayBothClocks();
18626       DrawPosition(FALSE, boards[currentMove]);
18627     }
18628   }
18629 }
18630
18631 static char cseq[12] = "\\   ";
18632
18633 Boolean
18634 set_cont_sequence (char *new_seq)
18635 {
18636     int len;
18637     Boolean ret;
18638
18639     // handle bad attempts to set the sequence
18640         if (!new_seq)
18641                 return 0; // acceptable error - no debug
18642
18643     len = strlen(new_seq);
18644     ret = (len > 0) && (len < sizeof(cseq));
18645     if (ret)
18646       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18647     else if (appData.debugMode)
18648       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18649     return ret;
18650 }
18651
18652 /*
18653     reformat a source message so words don't cross the width boundary.  internal
18654     newlines are not removed.  returns the wrapped size (no null character unless
18655     included in source message).  If dest is NULL, only calculate the size required
18656     for the dest buffer.  lp argument indicats line position upon entry, and it's
18657     passed back upon exit.
18658 */
18659 int
18660 wrap (char *dest, char *src, int count, int width, int *lp)
18661 {
18662     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18663
18664     cseq_len = strlen(cseq);
18665     old_line = line = *lp;
18666     ansi = len = clen = 0;
18667
18668     for (i=0; i < count; i++)
18669     {
18670         if (src[i] == '\033')
18671             ansi = 1;
18672
18673         // if we hit the width, back up
18674         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18675         {
18676             // store i & len in case the word is too long
18677             old_i = i, old_len = len;
18678
18679             // find the end of the last word
18680             while (i && src[i] != ' ' && src[i] != '\n')
18681             {
18682                 i--;
18683                 len--;
18684             }
18685
18686             // word too long?  restore i & len before splitting it
18687             if ((old_i-i+clen) >= width)
18688             {
18689                 i = old_i;
18690                 len = old_len;
18691             }
18692
18693             // extra space?
18694             if (i && src[i-1] == ' ')
18695                 len--;
18696
18697             if (src[i] != ' ' && src[i] != '\n')
18698             {
18699                 i--;
18700                 if (len)
18701                     len--;
18702             }
18703
18704             // now append the newline and continuation sequence
18705             if (dest)
18706                 dest[len] = '\n';
18707             len++;
18708             if (dest)
18709                 strncpy(dest+len, cseq, cseq_len);
18710             len += cseq_len;
18711             line = cseq_len;
18712             clen = cseq_len;
18713             continue;
18714         }
18715
18716         if (dest)
18717             dest[len] = src[i];
18718         len++;
18719         if (!ansi)
18720             line++;
18721         if (src[i] == '\n')
18722             line = 0;
18723         if (src[i] == 'm')
18724             ansi = 0;
18725     }
18726     if (dest && appData.debugMode)
18727     {
18728         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18729             count, width, line, len, *lp);
18730         show_bytes(debugFP, src, count);
18731         fprintf(debugFP, "\ndest: ");
18732         show_bytes(debugFP, dest, len);
18733         fprintf(debugFP, "\n");
18734     }
18735     *lp = dest ? line : old_line;
18736
18737     return len;
18738 }
18739
18740 // [HGM] vari: routines for shelving variations
18741 Boolean modeRestore = FALSE;
18742
18743 void
18744 PushInner (int firstMove, int lastMove)
18745 {
18746         int i, j, nrMoves = lastMove - firstMove;
18747
18748         // push current tail of game on stack
18749         savedResult[storedGames] = gameInfo.result;
18750         savedDetails[storedGames] = gameInfo.resultDetails;
18751         gameInfo.resultDetails = NULL;
18752         savedFirst[storedGames] = firstMove;
18753         savedLast [storedGames] = lastMove;
18754         savedFramePtr[storedGames] = framePtr;
18755         framePtr -= nrMoves; // reserve space for the boards
18756         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18757             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18758             for(j=0; j<MOVE_LEN; j++)
18759                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18760             for(j=0; j<2*MOVE_LEN; j++)
18761                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18762             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18763             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18764             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18765             pvInfoList[firstMove+i-1].depth = 0;
18766             commentList[framePtr+i] = commentList[firstMove+i];
18767             commentList[firstMove+i] = NULL;
18768         }
18769
18770         storedGames++;
18771         forwardMostMove = firstMove; // truncate game so we can start variation
18772 }
18773
18774 void
18775 PushTail (int firstMove, int lastMove)
18776 {
18777         if(appData.icsActive) { // only in local mode
18778                 forwardMostMove = currentMove; // mimic old ICS behavior
18779                 return;
18780         }
18781         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18782
18783         PushInner(firstMove, lastMove);
18784         if(storedGames == 1) GreyRevert(FALSE);
18785         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18786 }
18787
18788 void
18789 PopInner (Boolean annotate)
18790 {
18791         int i, j, nrMoves;
18792         char buf[8000], moveBuf[20];
18793
18794         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18795         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18796         nrMoves = savedLast[storedGames] - currentMove;
18797         if(annotate) {
18798                 int cnt = 10;
18799                 if(!WhiteOnMove(currentMove))
18800                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18801                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18802                 for(i=currentMove; i<forwardMostMove; i++) {
18803                         if(WhiteOnMove(i))
18804                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18805                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18806                         strcat(buf, moveBuf);
18807                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18808                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18809                 }
18810                 strcat(buf, ")");
18811         }
18812         for(i=1; i<=nrMoves; i++) { // copy last variation back
18813             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18814             for(j=0; j<MOVE_LEN; j++)
18815                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18816             for(j=0; j<2*MOVE_LEN; j++)
18817                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18818             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18819             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18820             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18821             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18822             commentList[currentMove+i] = commentList[framePtr+i];
18823             commentList[framePtr+i] = NULL;
18824         }
18825         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18826         framePtr = savedFramePtr[storedGames];
18827         gameInfo.result = savedResult[storedGames];
18828         if(gameInfo.resultDetails != NULL) {
18829             free(gameInfo.resultDetails);
18830       }
18831         gameInfo.resultDetails = savedDetails[storedGames];
18832         forwardMostMove = currentMove + nrMoves;
18833 }
18834
18835 Boolean
18836 PopTail (Boolean annotate)
18837 {
18838         if(appData.icsActive) return FALSE; // only in local mode
18839         if(!storedGames) return FALSE; // sanity
18840         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18841
18842         PopInner(annotate);
18843         if(currentMove < forwardMostMove) ForwardEvent(); else
18844         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18845
18846         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18847         return TRUE;
18848 }
18849
18850 void
18851 CleanupTail ()
18852 {       // remove all shelved variations
18853         int i;
18854         for(i=0; i<storedGames; i++) {
18855             if(savedDetails[i])
18856                 free(savedDetails[i]);
18857             savedDetails[i] = NULL;
18858         }
18859         for(i=framePtr; i<MAX_MOVES; i++) {
18860                 if(commentList[i]) free(commentList[i]);
18861                 commentList[i] = NULL;
18862         }
18863         framePtr = MAX_MOVES-1;
18864         storedGames = 0;
18865 }
18866
18867 void
18868 LoadVariation (int index, char *text)
18869 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18870         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18871         int level = 0, move;
18872
18873         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18874         // first find outermost bracketing variation
18875         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18876             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18877                 if(*p == '{') wait = '}'; else
18878                 if(*p == '[') wait = ']'; else
18879                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18880                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18881             }
18882             if(*p == wait) wait = NULLCHAR; // closing ]} found
18883             p++;
18884         }
18885         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18886         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18887         end[1] = NULLCHAR; // clip off comment beyond variation
18888         ToNrEvent(currentMove-1);
18889         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18890         // kludge: use ParsePV() to append variation to game
18891         move = currentMove;
18892         ParsePV(start, TRUE, TRUE);
18893         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18894         ClearPremoveHighlights();
18895         CommentPopDown();
18896         ToNrEvent(currentMove+1);
18897 }
18898
18899 void
18900 LoadTheme ()
18901 {
18902     char *p, *q, buf[MSG_SIZ];
18903     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18904         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18905         ParseArgsFromString(buf);
18906         ActivateTheme(TRUE); // also redo colors
18907         return;
18908     }
18909     p = nickName;
18910     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18911     {
18912         int len;
18913         q = appData.themeNames;
18914         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18915       if(appData.useBitmaps) {
18916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18917                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18918                 appData.liteBackTextureMode,
18919                 appData.darkBackTextureMode );
18920       } else {
18921         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18922                 Col2Text(2),   // lightSquareColor
18923                 Col2Text(3) ); // darkSquareColor
18924       }
18925       if(appData.useBorder) {
18926         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18927                 appData.border);
18928       } else {
18929         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18930       }
18931       if(appData.useFont) {
18932         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18933                 appData.renderPiecesWithFont,
18934                 appData.fontToPieceTable,
18935                 Col2Text(9),    // appData.fontBackColorWhite
18936                 Col2Text(10) ); // appData.fontForeColorBlack
18937       } else {
18938         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18939                 appData.pieceDirectory);
18940         if(!appData.pieceDirectory[0])
18941           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18942                 Col2Text(0),   // whitePieceColor
18943                 Col2Text(1) ); // blackPieceColor
18944       }
18945       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18946                 Col2Text(4),   // highlightSquareColor
18947                 Col2Text(5) ); // premoveHighlightColor
18948         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18949         if(insert != q) insert[-1] = NULLCHAR;
18950         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18951         if(q)   free(q);
18952     }
18953     ActivateTheme(FALSE);
18954 }