2a341463389f8be8652bbdc52727b14e31114d6d
[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 int deadRanks;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode;
917
918 void
919 LoadEngine ()
920 {
921     int i;
922     if(WaitForEngine(savCps, LoadEngine)) return;
923     CommonEngineInit(); // recalculate time odds
924     if(gameInfo.variant != StringToVariant(appData.variant)) {
925         // we changed variant when loading the engine; this forces us to reset
926         Reset(TRUE, savCps != &first);
927         oldMode = BeginningOfGame; // to prevent restoring old mode
928     }
929     InitChessProgram(savCps, FALSE);
930     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931     DisplayMessage("", "");
932     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934     ThawUI();
935     SetGNUMode();
936     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
937 }
938
939 void
940 ReplaceEngine (ChessProgramState *cps, int n)
941 {
942     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943     keepInfo = 1;
944     if(oldMode != BeginningOfGame) EditGameEvent();
945     keepInfo = 0;
946     UnloadEngine(cps);
947     appData.noChessProgram = FALSE;
948     appData.clockMode = TRUE;
949     InitEngine(cps, n);
950     UpdateLogos(TRUE);
951     if(n) return; // only startup first engine immediately; second can wait
952     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
953     LoadEngine();
954 }
955
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958
959 static char resetOptions[] =
960         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964
965 void
966 FloatToFront(char **list, char *engineLine)
967 {
968     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969     int i=0;
970     if(appData.recentEngines <= 0) return;
971     TidyProgramName(engineLine, "localhost", tidy+1);
972     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973     strncpy(buf+1, *list, MSG_SIZ-50);
974     if(p = strstr(buf, tidy)) { // tidy name appears in list
975         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976         while(*p++ = *++q); // squeeze out
977     }
978     strcat(tidy, buf+1); // put list behind tidy name
979     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981     ASSIGN(*list, tidy+1);
982 }
983
984 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
985
986 void
987 Load (ChessProgramState *cps, int i)
988 {
989     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
992         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
993         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
994         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
995         appData.firstProtocolVersion = PROTOVER;
996         ParseArgsFromString(buf);
997         SwapEngines(i);
998         ReplaceEngine(cps, i);
999         FloatToFront(&appData.recentEngineList, engineLine);
1000         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1001         return;
1002     }
1003     p = engineName;
1004     while(q = strchr(p, SLASH)) p = q+1;
1005     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1006     if(engineDir[0] != NULLCHAR) {
1007         ASSIGN(appData.directory[i], engineDir); p = engineName;
1008     } else if(p != engineName) { // derive directory from engine path, when not given
1009         p[-1] = 0;
1010         ASSIGN(appData.directory[i], engineName);
1011         p[-1] = SLASH;
1012         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1013     } else { ASSIGN(appData.directory[i], "."); }
1014     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1015     if(params[0]) {
1016         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1017         snprintf(command, MSG_SIZ, "%s %s", p, params);
1018         p = command;
1019     }
1020     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1021     ASSIGN(appData.chessProgram[i], p);
1022     appData.isUCI[i] = isUCI;
1023     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1024     appData.hasOwnBookUCI[i] = hasBook;
1025     if(!nickName[0]) useNick = FALSE;
1026     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1027     if(addToList) {
1028         int len;
1029         char quote;
1030         q = firstChessProgramNames;
1031         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1032         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1033         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1034                         quote, p, quote, appData.directory[i],
1035                         useNick ? " -fn \"" : "",
1036                         useNick ? nickName : "",
1037                         useNick ? "\"" : "",
1038                         v1 ? " -firstProtocolVersion 1" : "",
1039                         hasBook ? "" : " -fNoOwnBookUCI",
1040                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1041                         storeVariant ? " -variant " : "",
1042                         storeVariant ? VariantName(gameInfo.variant) : "");
1043         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1044         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1045         if(insert != q) insert[-1] = NULLCHAR;
1046         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1047         if(q)   free(q);
1048         FloatToFront(&appData.recentEngineList, buf);
1049     }
1050     ReplaceEngine(cps, i);
1051 }
1052
1053 void
1054 InitTimeControls ()
1055 {
1056     int matched, min, sec;
1057     /*
1058      * Parse timeControl resource
1059      */
1060     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1061                           appData.movesPerSession)) {
1062         char buf[MSG_SIZ];
1063         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1064         DisplayFatalError(buf, 0, 2);
1065     }
1066
1067     /*
1068      * Parse searchTime resource
1069      */
1070     if (*appData.searchTime != NULLCHAR) {
1071         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1072         if (matched == 1) {
1073             searchTime = min * 60;
1074         } else if (matched == 2) {
1075             searchTime = min * 60 + sec;
1076         } else {
1077             char buf[MSG_SIZ];
1078             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1079             DisplayFatalError(buf, 0, 2);
1080         }
1081     }
1082 }
1083
1084 void
1085 InitBackEnd1 ()
1086 {
1087
1088     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1089     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1090
1091     GetTimeMark(&programStartTime);
1092     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1093     appData.seedBase = random() + (random()<<15);
1094     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1095
1096     ClearProgramStats();
1097     programStats.ok_to_send = 1;
1098     programStats.seen_stat = 0;
1099
1100     /*
1101      * Initialize game list
1102      */
1103     ListNew(&gameList);
1104
1105
1106     /*
1107      * Internet chess server status
1108      */
1109     if (appData.icsActive) {
1110         appData.matchMode = FALSE;
1111         appData.matchGames = 0;
1112 #if ZIPPY
1113         appData.noChessProgram = !appData.zippyPlay;
1114 #else
1115         appData.zippyPlay = FALSE;
1116         appData.zippyTalk = FALSE;
1117         appData.noChessProgram = TRUE;
1118 #endif
1119         if (*appData.icsHelper != NULLCHAR) {
1120             appData.useTelnet = TRUE;
1121             appData.telnetProgram = appData.icsHelper;
1122         }
1123     } else {
1124         appData.zippyTalk = appData.zippyPlay = FALSE;
1125     }
1126
1127     /* [AS] Initialize pv info list [HGM] and game state */
1128     {
1129         int i, j;
1130
1131         for( i=0; i<=framePtr; i++ ) {
1132             pvInfoList[i].depth = -1;
1133             boards[i][EP_STATUS] = EP_NONE;
1134             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1135         }
1136     }
1137
1138     InitTimeControls();
1139
1140     /* [AS] Adjudication threshold */
1141     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1142
1143     InitEngine(&first, 0);
1144     InitEngine(&second, 1);
1145     CommonEngineInit();
1146
1147     pairing.which = "pairing"; // pairing engine
1148     pairing.pr = NoProc;
1149     pairing.isr = NULL;
1150     pairing.program = appData.pairingEngine;
1151     pairing.host = "localhost";
1152     pairing.dir = ".";
1153
1154     if (appData.icsActive) {
1155         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1156     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1157         appData.clockMode = FALSE;
1158         first.sendTime = second.sendTime = 0;
1159     }
1160
1161 #if ZIPPY
1162     /* Override some settings from environment variables, for backward
1163        compatibility.  Unfortunately it's not feasible to have the env
1164        vars just set defaults, at least in xboard.  Ugh.
1165     */
1166     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1167       ZippyInit();
1168     }
1169 #endif
1170
1171     if (!appData.icsActive) {
1172       char buf[MSG_SIZ];
1173       int len;
1174
1175       /* Check for variants that are supported only in ICS mode,
1176          or not at all.  Some that are accepted here nevertheless
1177          have bugs; see comments below.
1178       */
1179       VariantClass variant = StringToVariant(appData.variant);
1180       switch (variant) {
1181       case VariantBughouse:     /* need four players and two boards */
1182       case VariantKriegspiel:   /* need to hide pieces and move details */
1183         /* case VariantFischeRandom: (Fabien: moved below) */
1184         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1185         if( (len >= MSG_SIZ) && appData.debugMode )
1186           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1187
1188         DisplayFatalError(buf, 0, 2);
1189         return;
1190
1191       case VariantUnknown:
1192       case VariantLoadable:
1193       case Variant29:
1194       case Variant30:
1195       case Variant31:
1196       case Variant32:
1197       case Variant33:
1198       case Variant34:
1199       case Variant35:
1200       case Variant36:
1201       default:
1202         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1203         if( (len >= MSG_SIZ) && appData.debugMode )
1204           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1205
1206         DisplayFatalError(buf, 0, 2);
1207         return;
1208
1209       case VariantNormal:     /* definitely works! */
1210         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1211           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1212           return;
1213         }
1214       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1215       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1216       case VariantGothic:     /* [HGM] should work */
1217       case VariantCapablanca: /* [HGM] should work */
1218       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1219       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1220       case VariantChu:        /* [HGM] experimental */
1221       case VariantKnightmate: /* [HGM] should work */
1222       case VariantCylinder:   /* [HGM] untested */
1223       case VariantFalcon:     /* [HGM] untested */
1224       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1225                                  offboard interposition not understood */
1226       case VariantWildCastle: /* pieces not automatically shuffled */
1227       case VariantNoCastle:   /* pieces not automatically shuffled */
1228       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1229       case VariantLosers:     /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantSuicide:    /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantGiveaway:   /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantTwoKings:   /* should work */
1236       case VariantAtomic:     /* should work except for win condition */
1237       case Variant3Check:     /* should work except for win condition */
1238       case VariantShatranj:   /* should work except for all win conditions */
1239       case VariantMakruk:     /* should work except for draw countdown */
1240       case VariantASEAN :     /* should work except for draw countdown */
1241       case VariantBerolina:   /* might work if TestLegality is off */
1242       case VariantCapaRandom: /* should work */
1243       case VariantJanus:      /* should work */
1244       case VariantSuper:      /* experimental */
1245       case VariantGreat:      /* experimental, requires legality testing to be off */
1246       case VariantSChess:     /* S-Chess, should work */
1247       case VariantGrand:      /* should work */
1248       case VariantSpartan:    /* should work */
1249       case VariantLion:       /* should work */
1250       case VariantChuChess:   /* should work */
1251         break;
1252       }
1253     }
1254
1255 }
1256
1257 int
1258 NextIntegerFromString (char ** str, long * value)
1259 {
1260     int result = -1;
1261     char * s = *str;
1262
1263     while( *s == ' ' || *s == '\t' ) {
1264         s++;
1265     }
1266
1267     *value = 0;
1268
1269     if( *s >= '0' && *s <= '9' ) {
1270         while( *s >= '0' && *s <= '9' ) {
1271             *value = *value * 10 + (*s - '0');
1272             s++;
1273         }
1274
1275         result = 0;
1276     }
1277
1278     *str = s;
1279
1280     return result;
1281 }
1282
1283 int
1284 NextTimeControlFromString (char ** str, long * value)
1285 {
1286     long temp;
1287     int result = NextIntegerFromString( str, &temp );
1288
1289     if( result == 0 ) {
1290         *value = temp * 60; /* Minutes */
1291         if( **str == ':' ) {
1292             (*str)++;
1293             result = NextIntegerFromString( str, &temp );
1294             *value += temp; /* Seconds */
1295         }
1296     }
1297
1298     return result;
1299 }
1300
1301 int
1302 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1303 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1304     int result = -1, type = 0; long temp, temp2;
1305
1306     if(**str != ':') return -1; // old params remain in force!
1307     (*str)++;
1308     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1309     if( NextIntegerFromString( str, &temp ) ) return -1;
1310     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1311
1312     if(**str != '/') {
1313         /* time only: incremental or sudden-death time control */
1314         if(**str == '+') { /* increment follows; read it */
1315             (*str)++;
1316             if(**str == '!') type = *(*str)++; // Bronstein TC
1317             if(result = NextIntegerFromString( str, &temp2)) return -1;
1318             *inc = temp2 * 1000;
1319             if(**str == '.') { // read fraction of increment
1320                 char *start = ++(*str);
1321                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1322                 temp2 *= 1000;
1323                 while(start++ < *str) temp2 /= 10;
1324                 *inc += temp2;
1325             }
1326         } else *inc = 0;
1327         *moves = 0; *tc = temp * 1000; *incType = type;
1328         return 0;
1329     }
1330
1331     (*str)++; /* classical time control */
1332     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1333
1334     if(result == 0) {
1335         *moves = temp;
1336         *tc    = temp2 * 1000;
1337         *inc   = 0;
1338         *incType = type;
1339     }
1340     return result;
1341 }
1342
1343 int
1344 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1345 {   /* [HGM] get time to add from the multi-session time-control string */
1346     int incType, moves=1; /* kludge to force reading of first session */
1347     long time, increment;
1348     char *s = tcString;
1349
1350     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1351     do {
1352         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1353         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1354         if(movenr == -1) return time;    /* last move before new session     */
1355         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1356         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1357         if(!moves) return increment;     /* current session is incremental   */
1358         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1359     } while(movenr >= -1);               /* try again for next session       */
1360
1361     return 0; // no new time quota on this move
1362 }
1363
1364 int
1365 ParseTimeControl (char *tc, float ti, int mps)
1366 {
1367   long tc1;
1368   long tc2;
1369   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1370   int min, sec=0;
1371
1372   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1373   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1374       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1375   if(ti > 0) {
1376
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1381   } else {
1382     if(mps)
1383       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1384     else
1385       snprintf(buf, MSG_SIZ, ":%s", mytc);
1386   }
1387   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1388
1389   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1390     return FALSE;
1391   }
1392
1393   if( *tc == '/' ) {
1394     /* Parse second time control */
1395     tc++;
1396
1397     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1398       return FALSE;
1399     }
1400
1401     if( tc2 == 0 ) {
1402       return FALSE;
1403     }
1404
1405     timeControl_2 = tc2 * 1000;
1406   }
1407   else {
1408     timeControl_2 = 0;
1409   }
1410
1411   if( tc1 == 0 ) {
1412     return FALSE;
1413   }
1414
1415   timeControl = tc1 * 1000;
1416
1417   if (ti >= 0) {
1418     timeIncrement = ti * 1000;  /* convert to ms */
1419     movesPerSession = 0;
1420   } else {
1421     timeIncrement = 0;
1422     movesPerSession = mps;
1423   }
1424   return TRUE;
1425 }
1426
1427 void
1428 InitBackEnd2 ()
1429 {
1430     if (appData.debugMode) {
1431 #    ifdef __GIT_VERSION
1432       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1433 #    else
1434       fprintf(debugFP, "Version: %s\n", programVersion);
1435 #    endif
1436     }
1437     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1438
1439     set_cont_sequence(appData.wrapContSeq);
1440     if (appData.matchGames > 0) {
1441         appData.matchMode = TRUE;
1442     } else if (appData.matchMode) {
1443         appData.matchGames = 1;
1444     }
1445     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1446         appData.matchGames = appData.sameColorGames;
1447     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1448         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1449         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1450     }
1451     Reset(TRUE, FALSE);
1452     if (appData.noChessProgram || first.protocolVersion == 1) {
1453       InitBackEnd3();
1454     } else {
1455       /* kludge: allow timeout for initial "feature" commands */
1456       FreezeUI();
1457       DisplayMessage("", _("Starting chess program"));
1458       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1459     }
1460 }
1461
1462 int
1463 CalculateIndex (int index, int gameNr)
1464 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1465     int res;
1466     if(index > 0) return index; // fixed nmber
1467     if(index == 0) return 1;
1468     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1469     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1470     return res;
1471 }
1472
1473 int
1474 LoadGameOrPosition (int gameNr)
1475 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1476     if (*appData.loadGameFile != NULLCHAR) {
1477         if (!LoadGameFromFile(appData.loadGameFile,
1478                 CalculateIndex(appData.loadGameIndex, gameNr),
1479                               appData.loadGameFile, FALSE)) {
1480             DisplayFatalError(_("Bad game file"), 0, 1);
1481             return 0;
1482         }
1483     } else if (*appData.loadPositionFile != NULLCHAR) {
1484         if (!LoadPositionFromFile(appData.loadPositionFile,
1485                 CalculateIndex(appData.loadPositionIndex, gameNr),
1486                                   appData.loadPositionFile)) {
1487             DisplayFatalError(_("Bad position file"), 0, 1);
1488             return 0;
1489         }
1490     }
1491     return 1;
1492 }
1493
1494 void
1495 ReserveGame (int gameNr, char resChar)
1496 {
1497     FILE *tf = fopen(appData.tourneyFile, "r+");
1498     char *p, *q, c, buf[MSG_SIZ];
1499     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1500     safeStrCpy(buf, lastMsg, MSG_SIZ);
1501     DisplayMessage(_("Pick new game"), "");
1502     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1503     ParseArgsFromFile(tf);
1504     p = q = appData.results;
1505     if(appData.debugMode) {
1506       char *r = appData.participants;
1507       fprintf(debugFP, "results = '%s'\n", p);
1508       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1509       fprintf(debugFP, "\n");
1510     }
1511     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1512     nextGame = q - p;
1513     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1514     safeStrCpy(q, p, strlen(p) + 2);
1515     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1516     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1517     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1518         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1519         q[nextGame] = '*';
1520     }
1521     fseek(tf, -(strlen(p)+4), SEEK_END);
1522     c = fgetc(tf);
1523     if(c != '"') // depending on DOS or Unix line endings we can be one off
1524          fseek(tf, -(strlen(p)+2), SEEK_END);
1525     else fseek(tf, -(strlen(p)+3), SEEK_END);
1526     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1527     DisplayMessage(buf, "");
1528     free(p); appData.results = q;
1529     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1530        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1531       int round = appData.defaultMatchGames * appData.tourneyType;
1532       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1533          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1534         UnloadEngine(&first);  // next game belongs to other pairing;
1535         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1536     }
1537     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1538 }
1539
1540 void
1541 MatchEvent (int mode)
1542 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1543         int dummy;
1544         if(matchMode) { // already in match mode: switch it off
1545             abortMatch = TRUE;
1546             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1547             return;
1548         }
1549 //      if(gameMode != BeginningOfGame) {
1550 //          DisplayError(_("You can only start a match from the initial position."), 0);
1551 //          return;
1552 //      }
1553         abortMatch = FALSE;
1554         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1555         /* Set up machine vs. machine match */
1556         nextGame = 0;
1557         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1558         if(appData.tourneyFile[0]) {
1559             ReserveGame(-1, 0);
1560             if(nextGame > appData.matchGames) {
1561                 char buf[MSG_SIZ];
1562                 if(strchr(appData.results, '*') == NULL) {
1563                     FILE *f;
1564                     appData.tourneyCycles++;
1565                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1566                         fclose(f);
1567                         NextTourneyGame(-1, &dummy);
1568                         ReserveGame(-1, 0);
1569                         if(nextGame <= appData.matchGames) {
1570                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1571                             matchMode = mode;
1572                             ScheduleDelayedEvent(NextMatchGame, 10000);
1573                             return;
1574                         }
1575                     }
1576                 }
1577                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1578                 DisplayError(buf, 0);
1579                 appData.tourneyFile[0] = 0;
1580                 return;
1581             }
1582         } else
1583         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1584             DisplayFatalError(_("Can't have a match with no chess programs"),
1585                               0, 2);
1586             return;
1587         }
1588         matchMode = mode;
1589         matchGame = roundNr = 1;
1590         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1591         NextMatchGame();
1592 }
1593
1594 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1595
1596 void
1597 InitBackEnd3 P((void))
1598 {
1599     GameMode initialMode;
1600     char buf[MSG_SIZ];
1601     int err, len;
1602
1603     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1604        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1605         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1606        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1607        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1608         char c, *q = first.variants, *p = strchr(q, ',');
1609         if(p) *p = NULLCHAR;
1610         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1611             int w, h, s;
1612             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1613                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1614             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1615             Reset(TRUE, FALSE);         // and re-initialize
1616         }
1617         if(p) *p = ',';
1618     }
1619
1620     InitChessProgram(&first, startedFromSetupPosition);
1621
1622     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1623         free(programVersion);
1624         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1625         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1626         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1627     }
1628
1629     if (appData.icsActive) {
1630 #ifdef WIN32
1631         /* [DM] Make a console window if needed [HGM] merged ifs */
1632         ConsoleCreate();
1633 #endif
1634         err = establish();
1635         if (err != 0)
1636           {
1637             if (*appData.icsCommPort != NULLCHAR)
1638               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1639                              appData.icsCommPort);
1640             else
1641               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1642                         appData.icsHost, appData.icsPort);
1643
1644             if( (len >= MSG_SIZ) && appData.debugMode )
1645               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1646
1647             DisplayFatalError(buf, err, 1);
1648             return;
1649         }
1650         SetICSMode();
1651         telnetISR =
1652           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1653         fromUserISR =
1654           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1655         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1656             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1657     } else if (appData.noChessProgram) {
1658         SetNCPMode();
1659     } else {
1660         SetGNUMode();
1661     }
1662
1663     if (*appData.cmailGameName != NULLCHAR) {
1664         SetCmailMode();
1665         OpenLoopback(&cmailPR);
1666         cmailISR =
1667           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1668     }
1669
1670     ThawUI();
1671     DisplayMessage("", "");
1672     if (StrCaseCmp(appData.initialMode, "") == 0) {
1673       initialMode = BeginningOfGame;
1674       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1675         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1676         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1677         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1678         ModeHighlight();
1679       }
1680     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1681       initialMode = TwoMachinesPlay;
1682     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1683       initialMode = AnalyzeFile;
1684     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1685       initialMode = AnalyzeMode;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1687       initialMode = MachinePlaysWhite;
1688     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1689       initialMode = MachinePlaysBlack;
1690     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1691       initialMode = EditGame;
1692     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1693       initialMode = EditPosition;
1694     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1695       initialMode = Training;
1696     } else {
1697       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1698       if( (len >= MSG_SIZ) && appData.debugMode )
1699         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1700
1701       DisplayFatalError(buf, 0, 2);
1702       return;
1703     }
1704
1705     if (appData.matchMode) {
1706         if(appData.tourneyFile[0]) { // start tourney from command line
1707             FILE *f;
1708             if(f = fopen(appData.tourneyFile, "r")) {
1709                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1710                 fclose(f);
1711                 appData.clockMode = TRUE;
1712                 SetGNUMode();
1713             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1714         }
1715         MatchEvent(TRUE);
1716     } else if (*appData.cmailGameName != NULLCHAR) {
1717         /* Set up cmail mode */
1718         ReloadCmailMsgEvent(TRUE);
1719     } else {
1720         /* Set up other modes */
1721         if (initialMode == AnalyzeFile) {
1722           if (*appData.loadGameFile == NULLCHAR) {
1723             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1724             return;
1725           }
1726         }
1727         if (*appData.loadGameFile != NULLCHAR) {
1728             (void) LoadGameFromFile(appData.loadGameFile,
1729                                     appData.loadGameIndex,
1730                                     appData.loadGameFile, TRUE);
1731         } else if (*appData.loadPositionFile != NULLCHAR) {
1732             (void) LoadPositionFromFile(appData.loadPositionFile,
1733                                         appData.loadPositionIndex,
1734                                         appData.loadPositionFile);
1735             /* [HGM] try to make self-starting even after FEN load */
1736             /* to allow automatic setup of fairy variants with wtm */
1737             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1738                 gameMode = BeginningOfGame;
1739                 setboardSpoiledMachineBlack = 1;
1740             }
1741             /* [HGM] loadPos: make that every new game uses the setup */
1742             /* from file as long as we do not switch variant          */
1743             if(!blackPlaysFirst) {
1744                 startedFromPositionFile = TRUE;
1745                 CopyBoard(filePosition, boards[0]);
1746                 CopyBoard(initialPosition, boards[0]);
1747             }
1748         } else if(*appData.fen != NULLCHAR) {
1749             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1750                 startedFromPositionFile = TRUE;
1751                 Reset(TRUE, TRUE);
1752             }
1753         }
1754         if (initialMode == AnalyzeMode) {
1755           if (appData.noChessProgram) {
1756             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1757             return;
1758           }
1759           if (appData.icsActive) {
1760             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1761             return;
1762           }
1763           AnalyzeModeEvent();
1764         } else if (initialMode == AnalyzeFile) {
1765           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1766           ShowThinkingEvent();
1767           AnalyzeFileEvent();
1768           AnalysisPeriodicEvent(1);
1769         } else if (initialMode == MachinePlaysWhite) {
1770           if (appData.noChessProgram) {
1771             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1772                               0, 2);
1773             return;
1774           }
1775           if (appData.icsActive) {
1776             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1777                               0, 2);
1778             return;
1779           }
1780           MachineWhiteEvent();
1781         } else if (initialMode == MachinePlaysBlack) {
1782           if (appData.noChessProgram) {
1783             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1784                               0, 2);
1785             return;
1786           }
1787           if (appData.icsActive) {
1788             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1789                               0, 2);
1790             return;
1791           }
1792           MachineBlackEvent();
1793         } else if (initialMode == TwoMachinesPlay) {
1794           if (appData.noChessProgram) {
1795             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1796                               0, 2);
1797             return;
1798           }
1799           if (appData.icsActive) {
1800             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1801                               0, 2);
1802             return;
1803           }
1804           TwoMachinesEvent();
1805         } else if (initialMode == EditGame) {
1806           EditGameEvent();
1807         } else if (initialMode == EditPosition) {
1808           EditPositionEvent();
1809         } else if (initialMode == Training) {
1810           if (*appData.loadGameFile == NULLCHAR) {
1811             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1812             return;
1813           }
1814           TrainingEvent();
1815         }
1816     }
1817 }
1818
1819 void
1820 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1821 {
1822     DisplayBook(current+1);
1823
1824     MoveHistorySet( movelist, first, last, current, pvInfoList );
1825
1826     EvalGraphSet( first, last, current, pvInfoList );
1827
1828     MakeEngineOutputTitle();
1829 }
1830
1831 /*
1832  * Establish will establish a contact to a remote host.port.
1833  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1834  *  used to talk to the host.
1835  * Returns 0 if okay, error code if not.
1836  */
1837 int
1838 establish ()
1839 {
1840     char buf[MSG_SIZ];
1841
1842     if (*appData.icsCommPort != NULLCHAR) {
1843         /* Talk to the host through a serial comm port */
1844         return OpenCommPort(appData.icsCommPort, &icsPR);
1845
1846     } else if (*appData.gateway != NULLCHAR) {
1847         if (*appData.remoteShell == NULLCHAR) {
1848             /* Use the rcmd protocol to run telnet program on a gateway host */
1849             snprintf(buf, sizeof(buf), "%s %s %s",
1850                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1851             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1852
1853         } else {
1854             /* Use the rsh program to run telnet program on a gateway host */
1855             if (*appData.remoteUser == NULLCHAR) {
1856                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1857                         appData.gateway, appData.telnetProgram,
1858                         appData.icsHost, appData.icsPort);
1859             } else {
1860                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1861                         appData.remoteShell, appData.gateway,
1862                         appData.remoteUser, appData.telnetProgram,
1863                         appData.icsHost, appData.icsPort);
1864             }
1865             return StartChildProcess(buf, "", &icsPR);
1866
1867         }
1868     } else if (appData.useTelnet) {
1869         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1870
1871     } else {
1872         /* TCP socket interface differs somewhat between
1873            Unix and NT; handle details in the front end.
1874            */
1875         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1876     }
1877 }
1878
1879 void
1880 EscapeExpand (char *p, char *q)
1881 {       // [HGM] initstring: routine to shape up string arguments
1882         while(*p++ = *q++) if(p[-1] == '\\')
1883             switch(*q++) {
1884                 case 'n': p[-1] = '\n'; break;
1885                 case 'r': p[-1] = '\r'; break;
1886                 case 't': p[-1] = '\t'; break;
1887                 case '\\': p[-1] = '\\'; break;
1888                 case 0: *p = 0; return;
1889                 default: p[-1] = q[-1]; break;
1890             }
1891 }
1892
1893 void
1894 show_bytes (FILE *fp, char *buf, int count)
1895 {
1896     while (count--) {
1897         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1898             fprintf(fp, "\\%03o", *buf & 0xff);
1899         } else {
1900             putc(*buf, fp);
1901         }
1902         buf++;
1903     }
1904     fflush(fp);
1905 }
1906
1907 /* Returns an errno value */
1908 int
1909 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1910 {
1911     char buf[8192], *p, *q, *buflim;
1912     int left, newcount, outcount;
1913
1914     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1915         *appData.gateway != NULLCHAR) {
1916         if (appData.debugMode) {
1917             fprintf(debugFP, ">ICS: ");
1918             show_bytes(debugFP, message, count);
1919             fprintf(debugFP, "\n");
1920         }
1921         return OutputToProcess(pr, message, count, outError);
1922     }
1923
1924     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1925     p = message;
1926     q = buf;
1927     left = count;
1928     newcount = 0;
1929     while (left) {
1930         if (q >= buflim) {
1931             if (appData.debugMode) {
1932                 fprintf(debugFP, ">ICS: ");
1933                 show_bytes(debugFP, buf, newcount);
1934                 fprintf(debugFP, "\n");
1935             }
1936             outcount = OutputToProcess(pr, buf, newcount, outError);
1937             if (outcount < newcount) return -1; /* to be sure */
1938             q = buf;
1939             newcount = 0;
1940         }
1941         if (*p == '\n') {
1942             *q++ = '\r';
1943             newcount++;
1944         } else if (((unsigned char) *p) == TN_IAC) {
1945             *q++ = (char) TN_IAC;
1946             newcount ++;
1947         }
1948         *q++ = *p++;
1949         newcount++;
1950         left--;
1951     }
1952     if (appData.debugMode) {
1953         fprintf(debugFP, ">ICS: ");
1954         show_bytes(debugFP, buf, newcount);
1955         fprintf(debugFP, "\n");
1956     }
1957     outcount = OutputToProcess(pr, buf, newcount, outError);
1958     if (outcount < newcount) return -1; /* to be sure */
1959     return count;
1960 }
1961
1962 void
1963 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1964 {
1965     int outError, outCount;
1966     static int gotEof = 0;
1967     static FILE *ini;
1968
1969     /* Pass data read from player on to ICS */
1970     if (count > 0) {
1971         gotEof = 0;
1972         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1973         if (outCount < count) {
1974             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1975         }
1976         if(have_sent_ICS_logon == 2) {
1977           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1978             fprintf(ini, "%s", message);
1979             have_sent_ICS_logon = 3;
1980           } else
1981             have_sent_ICS_logon = 1;
1982         } else if(have_sent_ICS_logon == 3) {
1983             fprintf(ini, "%s", message);
1984             fclose(ini);
1985           have_sent_ICS_logon = 1;
1986         }
1987     } else if (count < 0) {
1988         RemoveInputSource(isr);
1989         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1990     } else if (gotEof++ > 0) {
1991         RemoveInputSource(isr);
1992         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1993     }
1994 }
1995
1996 void
1997 KeepAlive ()
1998 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1999     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2000     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2001     SendToICS("date\n");
2002     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2003 }
2004
2005 /* added routine for printf style output to ics */
2006 void
2007 ics_printf (char *format, ...)
2008 {
2009     char buffer[MSG_SIZ];
2010     va_list args;
2011
2012     va_start(args, format);
2013     vsnprintf(buffer, sizeof(buffer), format, args);
2014     buffer[sizeof(buffer)-1] = '\0';
2015     SendToICS(buffer);
2016     va_end(args);
2017 }
2018
2019 void
2020 SendToICS (char *s)
2021 {
2022     int count, outCount, outError;
2023
2024     if (icsPR == NoProc) return;
2025
2026     count = strlen(s);
2027     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2028     if (outCount < count) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033 /* This is used for sending logon scripts to the ICS. Sending
2034    without a delay causes problems when using timestamp on ICC
2035    (at least on my machine). */
2036 void
2037 SendToICSDelayed (char *s, long msdelay)
2038 {
2039     int count, outCount, outError;
2040
2041     if (icsPR == NoProc) return;
2042
2043     count = strlen(s);
2044     if (appData.debugMode) {
2045         fprintf(debugFP, ">ICS: ");
2046         show_bytes(debugFP, s, count);
2047         fprintf(debugFP, "\n");
2048     }
2049     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2050                                       msdelay);
2051     if (outCount < count) {
2052         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2053     }
2054 }
2055
2056
2057 /* Remove all highlighting escape sequences in s
2058    Also deletes any suffix starting with '('
2059    */
2060 char *
2061 StripHighlightAndTitle (char *s)
2062 {
2063     static char retbuf[MSG_SIZ];
2064     char *p = retbuf;
2065
2066     while (*s != NULLCHAR) {
2067         while (*s == '\033') {
2068             while (*s != NULLCHAR && !isalpha(*s)) s++;
2069             if (*s != NULLCHAR) s++;
2070         }
2071         while (*s != NULLCHAR && *s != '\033') {
2072             if (*s == '(' || *s == '[') {
2073                 *p = NULLCHAR;
2074                 return retbuf;
2075             }
2076             *p++ = *s++;
2077         }
2078     }
2079     *p = NULLCHAR;
2080     return retbuf;
2081 }
2082
2083 /* Remove all highlighting escape sequences in s */
2084 char *
2085 StripHighlight (char *s)
2086 {
2087     static char retbuf[MSG_SIZ];
2088     char *p = retbuf;
2089
2090     while (*s != NULLCHAR) {
2091         while (*s == '\033') {
2092             while (*s != NULLCHAR && !isalpha(*s)) s++;
2093             if (*s != NULLCHAR) s++;
2094         }
2095         while (*s != NULLCHAR && *s != '\033') {
2096             *p++ = *s++;
2097         }
2098     }
2099     *p = NULLCHAR;
2100     return retbuf;
2101 }
2102
2103 char engineVariant[MSG_SIZ];
2104 char *variantNames[] = VARIANT_NAMES;
2105 char *
2106 VariantName (VariantClass v)
2107 {
2108     if(v == VariantUnknown || *engineVariant) return engineVariant;
2109     return variantNames[v];
2110 }
2111
2112
2113 /* Identify a variant from the strings the chess servers use or the
2114    PGN Variant tag names we use. */
2115 VariantClass
2116 StringToVariant (char *e)
2117 {
2118     char *p;
2119     int wnum = -1;
2120     VariantClass v = VariantNormal;
2121     int i, found = FALSE;
2122     char buf[MSG_SIZ], c;
2123     int len;
2124
2125     if (!e) return v;
2126
2127     /* [HGM] skip over optional board-size prefixes */
2128     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2129         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2130         while( *e++ != '_');
2131     }
2132
2133     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2134         v = VariantNormal;
2135         found = TRUE;
2136     } else
2137     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2138       if (p = StrCaseStr(e, variantNames[i])) {
2139         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2140         v = (VariantClass) i;
2141         found = TRUE;
2142         break;
2143       }
2144     }
2145
2146     if (!found) {
2147       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2148           || StrCaseStr(e, "wild/fr")
2149           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2150         v = VariantFischeRandom;
2151       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2152                  (i = 1, p = StrCaseStr(e, "w"))) {
2153         p += i;
2154         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2155         if (isdigit(*p)) {
2156           wnum = atoi(p);
2157         } else {
2158           wnum = -1;
2159         }
2160         switch (wnum) {
2161         case 0: /* FICS only, actually */
2162         case 1:
2163           /* Castling legal even if K starts on d-file */
2164           v = VariantWildCastle;
2165           break;
2166         case 2:
2167         case 3:
2168         case 4:
2169           /* Castling illegal even if K & R happen to start in
2170              normal positions. */
2171           v = VariantNoCastle;
2172           break;
2173         case 5:
2174         case 7:
2175         case 8:
2176         case 10:
2177         case 11:
2178         case 12:
2179         case 13:
2180         case 14:
2181         case 15:
2182         case 18:
2183         case 19:
2184           /* Castling legal iff K & R start in normal positions */
2185           v = VariantNormal;
2186           break;
2187         case 6:
2188         case 20:
2189         case 21:
2190           /* Special wilds for position setup; unclear what to do here */
2191           v = VariantLoadable;
2192           break;
2193         case 9:
2194           /* Bizarre ICC game */
2195           v = VariantTwoKings;
2196           break;
2197         case 16:
2198           v = VariantKriegspiel;
2199           break;
2200         case 17:
2201           v = VariantLosers;
2202           break;
2203         case 22:
2204           v = VariantFischeRandom;
2205           break;
2206         case 23:
2207           v = VariantCrazyhouse;
2208           break;
2209         case 24:
2210           v = VariantBughouse;
2211           break;
2212         case 25:
2213           v = Variant3Check;
2214           break;
2215         case 26:
2216           /* Not quite the same as FICS suicide! */
2217           v = VariantGiveaway;
2218           break;
2219         case 27:
2220           v = VariantAtomic;
2221           break;
2222         case 28:
2223           v = VariantShatranj;
2224           break;
2225
2226         /* Temporary names for future ICC types.  The name *will* change in
2227            the next xboard/WinBoard release after ICC defines it. */
2228         case 29:
2229           v = Variant29;
2230           break;
2231         case 30:
2232           v = Variant30;
2233           break;
2234         case 31:
2235           v = Variant31;
2236           break;
2237         case 32:
2238           v = Variant32;
2239           break;
2240         case 33:
2241           v = Variant33;
2242           break;
2243         case 34:
2244           v = Variant34;
2245           break;
2246         case 35:
2247           v = Variant35;
2248           break;
2249         case 36:
2250           v = Variant36;
2251           break;
2252         case 37:
2253           v = VariantShogi;
2254           break;
2255         case 38:
2256           v = VariantXiangqi;
2257           break;
2258         case 39:
2259           v = VariantCourier;
2260           break;
2261         case 40:
2262           v = VariantGothic;
2263           break;
2264         case 41:
2265           v = VariantCapablanca;
2266           break;
2267         case 42:
2268           v = VariantKnightmate;
2269           break;
2270         case 43:
2271           v = VariantFairy;
2272           break;
2273         case 44:
2274           v = VariantCylinder;
2275           break;
2276         case 45:
2277           v = VariantFalcon;
2278           break;
2279         case 46:
2280           v = VariantCapaRandom;
2281           break;
2282         case 47:
2283           v = VariantBerolina;
2284           break;
2285         case 48:
2286           v = VariantJanus;
2287           break;
2288         case 49:
2289           v = VariantSuper;
2290           break;
2291         case 50:
2292           v = VariantGreat;
2293           break;
2294         case -1:
2295           /* Found "wild" or "w" in the string but no number;
2296              must assume it's normal chess. */
2297           v = VariantNormal;
2298           break;
2299         default:
2300           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2301           if( (len >= MSG_SIZ) && appData.debugMode )
2302             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2303
2304           DisplayError(buf, 0);
2305           v = VariantUnknown;
2306           break;
2307         }
2308       }
2309     }
2310     if (appData.debugMode) {
2311       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2312               e, wnum, VariantName(v));
2313     }
2314     return v;
2315 }
2316
2317 static int leftover_start = 0, leftover_len = 0;
2318 char star_match[STAR_MATCH_N][MSG_SIZ];
2319
2320 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2321    advance *index beyond it, and set leftover_start to the new value of
2322    *index; else return FALSE.  If pattern contains the character '*', it
2323    matches any sequence of characters not containing '\r', '\n', or the
2324    character following the '*' (if any), and the matched sequence(s) are
2325    copied into star_match.
2326    */
2327 int
2328 looking_at ( char *buf, int *index, char *pattern)
2329 {
2330     char *bufp = &buf[*index], *patternp = pattern;
2331     int star_count = 0;
2332     char *matchp = star_match[0];
2333
2334     for (;;) {
2335         if (*patternp == NULLCHAR) {
2336             *index = leftover_start = bufp - buf;
2337             *matchp = NULLCHAR;
2338             return TRUE;
2339         }
2340         if (*bufp == NULLCHAR) return FALSE;
2341         if (*patternp == '*') {
2342             if (*bufp == *(patternp + 1)) {
2343                 *matchp = NULLCHAR;
2344                 matchp = star_match[++star_count];
2345                 patternp += 2;
2346                 bufp++;
2347                 continue;
2348             } else if (*bufp == '\n' || *bufp == '\r') {
2349                 patternp++;
2350                 if (*patternp == NULLCHAR)
2351                   continue;
2352                 else
2353                   return FALSE;
2354             } else {
2355                 *matchp++ = *bufp++;
2356                 continue;
2357             }
2358         }
2359         if (*patternp != *bufp) return FALSE;
2360         patternp++;
2361         bufp++;
2362     }
2363 }
2364
2365 void
2366 SendToPlayer (char *data, int length)
2367 {
2368     int error, outCount;
2369     outCount = OutputToProcess(NoProc, data, length, &error);
2370     if (outCount < length) {
2371         DisplayFatalError(_("Error writing to display"), error, 1);
2372     }
2373 }
2374
2375 void
2376 PackHolding (char packed[], char *holding)
2377 {
2378     char *p = holding;
2379     char *q = packed;
2380     int runlength = 0;
2381     int curr = 9999;
2382     do {
2383         if (*p == curr) {
2384             runlength++;
2385         } else {
2386             switch (runlength) {
2387               case 0:
2388                 break;
2389               case 1:
2390                 *q++ = curr;
2391                 break;
2392               case 2:
2393                 *q++ = curr;
2394                 *q++ = curr;
2395                 break;
2396               default:
2397                 sprintf(q, "%d", runlength);
2398                 while (*q) q++;
2399                 *q++ = curr;
2400                 break;
2401             }
2402             runlength = 1;
2403             curr = *p;
2404         }
2405     } while (*p++);
2406     *q = NULLCHAR;
2407 }
2408
2409 /* Telnet protocol requests from the front end */
2410 void
2411 TelnetRequest (unsigned char ddww, unsigned char option)
2412 {
2413     unsigned char msg[3];
2414     int outCount, outError;
2415
2416     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2417
2418     if (appData.debugMode) {
2419         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2420         switch (ddww) {
2421           case TN_DO:
2422             ddwwStr = "DO";
2423             break;
2424           case TN_DONT:
2425             ddwwStr = "DONT";
2426             break;
2427           case TN_WILL:
2428             ddwwStr = "WILL";
2429             break;
2430           case TN_WONT:
2431             ddwwStr = "WONT";
2432             break;
2433           default:
2434             ddwwStr = buf1;
2435             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2436             break;
2437         }
2438         switch (option) {
2439           case TN_ECHO:
2440             optionStr = "ECHO";
2441             break;
2442           default:
2443             optionStr = buf2;
2444             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2445             break;
2446         }
2447         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2448     }
2449     msg[0] = TN_IAC;
2450     msg[1] = ddww;
2451     msg[2] = option;
2452     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2453     if (outCount < 3) {
2454         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2455     }
2456 }
2457
2458 void
2459 DoEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DO, TN_ECHO);
2463 }
2464
2465 void
2466 DontEcho ()
2467 {
2468     if (!appData.icsActive) return;
2469     TelnetRequest(TN_DONT, TN_ECHO);
2470 }
2471
2472 void
2473 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2474 {
2475     /* put the holdings sent to us by the server on the board holdings area */
2476     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2477     char p;
2478     ChessSquare piece;
2479
2480     if(gameInfo.holdingsWidth < 2)  return;
2481     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2482         return; // prevent overwriting by pre-board holdings
2483
2484     if( (int)lowestPiece >= BlackPawn ) {
2485         holdingsColumn = 0;
2486         countsColumn = 1;
2487         holdingsStartRow = BOARD_HEIGHT-1;
2488         direction = -1;
2489     } else {
2490         holdingsColumn = BOARD_WIDTH-1;
2491         countsColumn = BOARD_WIDTH-2;
2492         holdingsStartRow = 0;
2493         direction = 1;
2494     }
2495
2496     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2497         board[i][holdingsColumn] = EmptySquare;
2498         board[i][countsColumn]   = (ChessSquare) 0;
2499     }
2500     while( (p=*holdings++) != NULLCHAR ) {
2501         piece = CharToPiece( ToUpper(p) );
2502         if(piece == EmptySquare) continue;
2503         /*j = (int) piece - (int) WhitePawn;*/
2504         j = PieceToNumber(piece);
2505         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2506         if(j < 0) continue;               /* should not happen */
2507         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2508         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2509         board[holdingsStartRow+j*direction][countsColumn]++;
2510     }
2511 }
2512
2513
2514 void
2515 VariantSwitch (Board board, VariantClass newVariant)
2516 {
2517    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2518    static Board oldBoard;
2519
2520    startedFromPositionFile = FALSE;
2521    if(gameInfo.variant == newVariant) return;
2522
2523    /* [HGM] This routine is called each time an assignment is made to
2524     * gameInfo.variant during a game, to make sure the board sizes
2525     * are set to match the new variant. If that means adding or deleting
2526     * holdings, we shift the playing board accordingly
2527     * This kludge is needed because in ICS observe mode, we get boards
2528     * of an ongoing game without knowing the variant, and learn about the
2529     * latter only later. This can be because of the move list we requested,
2530     * in which case the game history is refilled from the beginning anyway,
2531     * but also when receiving holdings of a crazyhouse game. In the latter
2532     * case we want to add those holdings to the already received position.
2533     */
2534
2535
2536    if (appData.debugMode) {
2537      fprintf(debugFP, "Switch board from %s to %s\n",
2538              VariantName(gameInfo.variant), VariantName(newVariant));
2539      setbuf(debugFP, NULL);
2540    }
2541    shuffleOpenings = 0;       /* [HGM] shuffle */
2542    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2543    switch(newVariant)
2544      {
2545      case VariantShogi:
2546        newWidth = 9;  newHeight = 9;
2547        gameInfo.holdingsSize = 7;
2548      case VariantBughouse:
2549      case VariantCrazyhouse:
2550        newHoldingsWidth = 2; break;
2551      case VariantGreat:
2552        newWidth = 10;
2553      case VariantSuper:
2554        newHoldingsWidth = 2;
2555        gameInfo.holdingsSize = 8;
2556        break;
2557      case VariantGothic:
2558      case VariantCapablanca:
2559      case VariantCapaRandom:
2560        newWidth = 10;
2561      default:
2562        newHoldingsWidth = gameInfo.holdingsSize = 0;
2563      };
2564
2565    if(newWidth  != gameInfo.boardWidth  ||
2566       newHeight != gameInfo.boardHeight ||
2567       newHoldingsWidth != gameInfo.holdingsWidth ) {
2568
2569      /* shift position to new playing area, if needed */
2570      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2571        for(i=0; i<BOARD_HEIGHT; i++)
2572          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2573            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2574              board[i][j];
2575        for(i=0; i<newHeight; i++) {
2576          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2577          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2578        }
2579      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2580        for(i=0; i<BOARD_HEIGHT; i++)
2581          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2582            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2583              board[i][j];
2584      }
2585      board[HOLDINGS_SET] = 0;
2586      gameInfo.boardWidth  = newWidth;
2587      gameInfo.boardHeight = newHeight;
2588      gameInfo.holdingsWidth = newHoldingsWidth;
2589      gameInfo.variant = newVariant;
2590      InitDrawingSizes(-2, 0);
2591    } else gameInfo.variant = newVariant;
2592    CopyBoard(oldBoard, board);   // remember correctly formatted board
2593      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2594    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2595 }
2596
2597 static int loggedOn = FALSE;
2598
2599 /*-- Game start info cache: --*/
2600 int gs_gamenum;
2601 char gs_kind[MSG_SIZ];
2602 static char player1Name[128] = "";
2603 static char player2Name[128] = "";
2604 static char cont_seq[] = "\n\\   ";
2605 static int player1Rating = -1;
2606 static int player2Rating = -1;
2607 /*----------------------------*/
2608
2609 ColorClass curColor = ColorNormal;
2610 int suppressKibitz = 0;
2611
2612 // [HGM] seekgraph
2613 Boolean soughtPending = FALSE;
2614 Boolean seekGraphUp;
2615 #define MAX_SEEK_ADS 200
2616 #define SQUARE 0x80
2617 char *seekAdList[MAX_SEEK_ADS];
2618 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2619 float tcList[MAX_SEEK_ADS];
2620 char colorList[MAX_SEEK_ADS];
2621 int nrOfSeekAds = 0;
2622 int minRating = 1010, maxRating = 2800;
2623 int hMargin = 10, vMargin = 20, h, w;
2624 extern int squareSize, lineGap;
2625
2626 void
2627 PlotSeekAd (int i)
2628 {
2629         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2630         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2631         if(r < minRating+100 && r >=0 ) r = minRating+100;
2632         if(r > maxRating) r = maxRating;
2633         if(tc < 1.f) tc = 1.f;
2634         if(tc > 95.f) tc = 95.f;
2635         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2636         y = ((double)r - minRating)/(maxRating - minRating)
2637             * (h-vMargin-squareSize/8-1) + vMargin;
2638         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2639         if(strstr(seekAdList[i], " u ")) color = 1;
2640         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2641            !strstr(seekAdList[i], "bullet") &&
2642            !strstr(seekAdList[i], "blitz") &&
2643            !strstr(seekAdList[i], "standard") ) color = 2;
2644         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2645         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2646 }
2647
2648 void
2649 PlotSingleSeekAd (int i)
2650 {
2651         PlotSeekAd(i);
2652 }
2653
2654 void
2655 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2656 {
2657         char buf[MSG_SIZ], *ext = "";
2658         VariantClass v = StringToVariant(type);
2659         if(strstr(type, "wild")) {
2660             ext = type + 4; // append wild number
2661             if(v == VariantFischeRandom) type = "chess960"; else
2662             if(v == VariantLoadable) type = "setup"; else
2663             type = VariantName(v);
2664         }
2665         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2666         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2667             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2668             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2669             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2670             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2671             seekNrList[nrOfSeekAds] = nr;
2672             zList[nrOfSeekAds] = 0;
2673             seekAdList[nrOfSeekAds++] = StrSave(buf);
2674             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2675         }
2676 }
2677
2678 void
2679 EraseSeekDot (int i)
2680 {
2681     int x = xList[i], y = yList[i], d=squareSize/4, k;
2682     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2683     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2684     // now replot every dot that overlapped
2685     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2686         int xx = xList[k], yy = yList[k];
2687         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2688             DrawSeekDot(xx, yy, colorList[k]);
2689     }
2690 }
2691
2692 void
2693 RemoveSeekAd (int nr)
2694 {
2695         int i;
2696         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2697             EraseSeekDot(i);
2698             if(seekAdList[i]) free(seekAdList[i]);
2699             seekAdList[i] = seekAdList[--nrOfSeekAds];
2700             seekNrList[i] = seekNrList[nrOfSeekAds];
2701             ratingList[i] = ratingList[nrOfSeekAds];
2702             colorList[i]  = colorList[nrOfSeekAds];
2703             tcList[i] = tcList[nrOfSeekAds];
2704             xList[i]  = xList[nrOfSeekAds];
2705             yList[i]  = yList[nrOfSeekAds];
2706             zList[i]  = zList[nrOfSeekAds];
2707             seekAdList[nrOfSeekAds] = NULL;
2708             break;
2709         }
2710 }
2711
2712 Boolean
2713 MatchSoughtLine (char *line)
2714 {
2715     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2716     int nr, base, inc, u=0; char dummy;
2717
2718     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2719        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2720        (u=1) &&
2721        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2722         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2723         // match: compact and save the line
2724         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2725         return TRUE;
2726     }
2727     return FALSE;
2728 }
2729
2730 int
2731 DrawSeekGraph ()
2732 {
2733     int i;
2734     if(!seekGraphUp) return FALSE;
2735     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2736     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2737
2738     DrawSeekBackground(0, 0, w, h);
2739     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2740     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2741     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2742         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2743         yy = h-1-yy;
2744         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2745         if(i%500 == 0) {
2746             char buf[MSG_SIZ];
2747             snprintf(buf, MSG_SIZ, "%d", i);
2748             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2749         }
2750     }
2751     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2752     for(i=1; i<100; i+=(i<10?1:5)) {
2753         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2754         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2755         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2756             char buf[MSG_SIZ];
2757             snprintf(buf, MSG_SIZ, "%d", i);
2758             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2759         }
2760     }
2761     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2762     return TRUE;
2763 }
2764
2765 int
2766 SeekGraphClick (ClickType click, int x, int y, int moving)
2767 {
2768     static int lastDown = 0, displayed = 0, lastSecond;
2769     if(y < 0) return FALSE;
2770     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2771         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2772         if(!seekGraphUp) return FALSE;
2773         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2774         DrawPosition(TRUE, NULL);
2775         return TRUE;
2776     }
2777     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2778         if(click == Release || moving) return FALSE;
2779         nrOfSeekAds = 0;
2780         soughtPending = TRUE;
2781         SendToICS(ics_prefix);
2782         SendToICS("sought\n"); // should this be "sought all"?
2783     } else { // issue challenge based on clicked ad
2784         int dist = 10000; int i, closest = 0, second = 0;
2785         for(i=0; i<nrOfSeekAds; i++) {
2786             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2787             if(d < dist) { dist = d; closest = i; }
2788             second += (d - zList[i] < 120); // count in-range ads
2789             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2790         }
2791         if(dist < 120) {
2792             char buf[MSG_SIZ];
2793             second = (second > 1);
2794             if(displayed != closest || second != lastSecond) {
2795                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2796                 lastSecond = second; displayed = closest;
2797             }
2798             if(click == Press) {
2799                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2800                 lastDown = closest;
2801                 return TRUE;
2802             } // on press 'hit', only show info
2803             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2804             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2805             SendToICS(ics_prefix);
2806             SendToICS(buf);
2807             return TRUE; // let incoming board of started game pop down the graph
2808         } else if(click == Release) { // release 'miss' is ignored
2809             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2810             if(moving == 2) { // right up-click
2811                 nrOfSeekAds = 0; // refresh graph
2812                 soughtPending = TRUE;
2813                 SendToICS(ics_prefix);
2814                 SendToICS("sought\n"); // should this be "sought all"?
2815             }
2816             return TRUE;
2817         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2818         // press miss or release hit 'pop down' seek graph
2819         seekGraphUp = FALSE;
2820         DrawPosition(TRUE, NULL);
2821     }
2822     return TRUE;
2823 }
2824
2825 void
2826 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2827 {
2828 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2829 #define STARTED_NONE 0
2830 #define STARTED_MOVES 1
2831 #define STARTED_BOARD 2
2832 #define STARTED_OBSERVE 3
2833 #define STARTED_HOLDINGS 4
2834 #define STARTED_CHATTER 5
2835 #define STARTED_COMMENT 6
2836 #define STARTED_MOVES_NOHIDE 7
2837
2838     static int started = STARTED_NONE;
2839     static char parse[20000];
2840     static int parse_pos = 0;
2841     static char buf[BUF_SIZE + 1];
2842     static int firstTime = TRUE, intfSet = FALSE;
2843     static ColorClass prevColor = ColorNormal;
2844     static int savingComment = FALSE;
2845     static int cmatch = 0; // continuation sequence match
2846     char *bp;
2847     char str[MSG_SIZ];
2848     int i, oldi;
2849     int buf_len;
2850     int next_out;
2851     int tkind;
2852     int backup;    /* [DM] For zippy color lines */
2853     char *p;
2854     char talker[MSG_SIZ]; // [HGM] chat
2855     int channel, collective=0;
2856
2857     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2858
2859     if (appData.debugMode) {
2860       if (!error) {
2861         fprintf(debugFP, "<ICS: ");
2862         show_bytes(debugFP, data, count);
2863         fprintf(debugFP, "\n");
2864       }
2865     }
2866
2867     if (appData.debugMode) { int f = forwardMostMove;
2868         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2869                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2870                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2871     }
2872     if (count > 0) {
2873         /* If last read ended with a partial line that we couldn't parse,
2874            prepend it to the new read and try again. */
2875         if (leftover_len > 0) {
2876             for (i=0; i<leftover_len; i++)
2877               buf[i] = buf[leftover_start + i];
2878         }
2879
2880     /* copy new characters into the buffer */
2881     bp = buf + leftover_len;
2882     buf_len=leftover_len;
2883     for (i=0; i<count; i++)
2884     {
2885         // ignore these
2886         if (data[i] == '\r')
2887             continue;
2888
2889         // join lines split by ICS?
2890         if (!appData.noJoin)
2891         {
2892             /*
2893                 Joining just consists of finding matches against the
2894                 continuation sequence, and discarding that sequence
2895                 if found instead of copying it.  So, until a match
2896                 fails, there's nothing to do since it might be the
2897                 complete sequence, and thus, something we don't want
2898                 copied.
2899             */
2900             if (data[i] == cont_seq[cmatch])
2901             {
2902                 cmatch++;
2903                 if (cmatch == strlen(cont_seq))
2904                 {
2905                     cmatch = 0; // complete match.  just reset the counter
2906
2907                     /*
2908                         it's possible for the ICS to not include the space
2909                         at the end of the last word, making our [correct]
2910                         join operation fuse two separate words.  the server
2911                         does this when the space occurs at the width setting.
2912                     */
2913                     if (!buf_len || buf[buf_len-1] != ' ')
2914                     {
2915                         *bp++ = ' ';
2916                         buf_len++;
2917                     }
2918                 }
2919                 continue;
2920             }
2921             else if (cmatch)
2922             {
2923                 /*
2924                     match failed, so we have to copy what matched before
2925                     falling through and copying this character.  In reality,
2926                     this will only ever be just the newline character, but
2927                     it doesn't hurt to be precise.
2928                 */
2929                 strncpy(bp, cont_seq, cmatch);
2930                 bp += cmatch;
2931                 buf_len += cmatch;
2932                 cmatch = 0;
2933             }
2934         }
2935
2936         // copy this char
2937         *bp++ = data[i];
2938         buf_len++;
2939     }
2940
2941         buf[buf_len] = NULLCHAR;
2942 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2943         next_out = 0;
2944         leftover_start = 0;
2945
2946         i = 0;
2947         while (i < buf_len) {
2948             /* Deal with part of the TELNET option negotiation
2949                protocol.  We refuse to do anything beyond the
2950                defaults, except that we allow the WILL ECHO option,
2951                which ICS uses to turn off password echoing when we are
2952                directly connected to it.  We reject this option
2953                if localLineEditing mode is on (always on in xboard)
2954                and we are talking to port 23, which might be a real
2955                telnet server that will try to keep WILL ECHO on permanently.
2956              */
2957             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2958                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2959                 unsigned char option;
2960                 oldi = i;
2961                 switch ((unsigned char) buf[++i]) {
2962                   case TN_WILL:
2963                     if (appData.debugMode)
2964                       fprintf(debugFP, "\n<WILL ");
2965                     switch (option = (unsigned char) buf[++i]) {
2966                       case TN_ECHO:
2967                         if (appData.debugMode)
2968                           fprintf(debugFP, "ECHO ");
2969                         /* Reply only if this is a change, according
2970                            to the protocol rules. */
2971                         if (remoteEchoOption) break;
2972                         if (appData.localLineEditing &&
2973                             atoi(appData.icsPort) == TN_PORT) {
2974                             TelnetRequest(TN_DONT, TN_ECHO);
2975                         } else {
2976                             EchoOff();
2977                             TelnetRequest(TN_DO, TN_ECHO);
2978                             remoteEchoOption = TRUE;
2979                         }
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", option);
2984                         /* Whatever this is, we don't want it. */
2985                         TelnetRequest(TN_DONT, option);
2986                         break;
2987                     }
2988                     break;
2989                   case TN_WONT:
2990                     if (appData.debugMode)
2991                       fprintf(debugFP, "\n<WONT ");
2992                     switch (option = (unsigned char) buf[++i]) {
2993                       case TN_ECHO:
2994                         if (appData.debugMode)
2995                           fprintf(debugFP, "ECHO ");
2996                         /* Reply only if this is a change, according
2997                            to the protocol rules. */
2998                         if (!remoteEchoOption) break;
2999                         EchoOn();
3000                         TelnetRequest(TN_DONT, TN_ECHO);
3001                         remoteEchoOption = FALSE;
3002                         break;
3003                       default:
3004                         if (appData.debugMode)
3005                           fprintf(debugFP, "%d ", (unsigned char) option);
3006                         /* Whatever this is, it must already be turned
3007                            off, because we never agree to turn on
3008                            anything non-default, so according to the
3009                            protocol rules, we don't reply. */
3010                         break;
3011                     }
3012                     break;
3013                   case TN_DO:
3014                     if (appData.debugMode)
3015                       fprintf(debugFP, "\n<DO ");
3016                     switch (option = (unsigned char) buf[++i]) {
3017                       default:
3018                         /* Whatever this is, we refuse to do it. */
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         TelnetRequest(TN_WONT, option);
3022                         break;
3023                     }
3024                     break;
3025                   case TN_DONT:
3026                     if (appData.debugMode)
3027                       fprintf(debugFP, "\n<DONT ");
3028                     switch (option = (unsigned char) buf[++i]) {
3029                       default:
3030                         if (appData.debugMode)
3031                           fprintf(debugFP, "%d ", option);
3032                         /* Whatever this is, we are already not doing
3033                            it, because we never agree to do anything
3034                            non-default, so according to the protocol
3035                            rules, we don't reply. */
3036                         break;
3037                     }
3038                     break;
3039                   case TN_IAC:
3040                     if (appData.debugMode)
3041                       fprintf(debugFP, "\n<IAC ");
3042                     /* Doubled IAC; pass it through */
3043                     i--;
3044                     break;
3045                   default:
3046                     if (appData.debugMode)
3047                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3048                     /* Drop all other telnet commands on the floor */
3049                     break;
3050                 }
3051                 if (oldi > next_out)
3052                   SendToPlayer(&buf[next_out], oldi - next_out);
3053                 if (++i > next_out)
3054                   next_out = i;
3055                 continue;
3056             }
3057
3058             /* OK, this at least will *usually* work */
3059             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3060                 loggedOn = TRUE;
3061             }
3062
3063             if (loggedOn && !intfSet) {
3064                 if (ics_type == ICS_ICC) {
3065                   snprintf(str, MSG_SIZ,
3066                           "/set-quietly interface %s\n/set-quietly style 12\n",
3067                           programVersion);
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3070                 } else if (ics_type == ICS_CHESSNET) {
3071                   snprintf(str, MSG_SIZ, "/style 12\n");
3072                 } else {
3073                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3074                   strcat(str, programVersion);
3075                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3076                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3077                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3078 #ifdef WIN32
3079                   strcat(str, "$iset nohighlight 1\n");
3080 #endif
3081                   strcat(str, "$iset lock 1\n$style 12\n");
3082                 }
3083                 SendToICS(str);
3084                 NotifyFrontendLogin();
3085                 intfSet = TRUE;
3086             }
3087
3088             if (started == STARTED_COMMENT) {
3089                 /* Accumulate characters in comment */
3090                 parse[parse_pos++] = buf[i];
3091                 if (buf[i] == '\n') {
3092                     parse[parse_pos] = NULLCHAR;
3093                     if(chattingPartner>=0) {
3094                         char mess[MSG_SIZ];
3095                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3096                         OutputChatMessage(chattingPartner, mess);
3097                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3098                             int p;
3099                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3100                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3101                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3102                                 OutputChatMessage(p, mess);
3103                                 break;
3104                             }
3105                         }
3106                         chattingPartner = -1;
3107                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3108                         collective = 0;
3109                     } else
3110                     if(!suppressKibitz) // [HGM] kibitz
3111                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3112                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3113                         int nrDigit = 0, nrAlph = 0, j;
3114                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3115                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3116                         parse[parse_pos] = NULLCHAR;
3117                         // try to be smart: if it does not look like search info, it should go to
3118                         // ICS interaction window after all, not to engine-output window.
3119                         for(j=0; j<parse_pos; j++) { // count letters and digits
3120                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3121                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3122                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3123                         }
3124                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3125                             int depth=0; float score;
3126                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3127                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3128                                 pvInfoList[forwardMostMove-1].depth = depth;
3129                                 pvInfoList[forwardMostMove-1].score = 100*score;
3130                             }
3131                             OutputKibitz(suppressKibitz, parse);
3132                         } else {
3133                             char tmp[MSG_SIZ];
3134                             if(gameMode == IcsObserving) // restore original ICS messages
3135                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3136                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3137                             else
3138                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3139                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3140                             SendToPlayer(tmp, strlen(tmp));
3141                         }
3142                         next_out = i+1; // [HGM] suppress printing in ICS window
3143                     }
3144                     started = STARTED_NONE;
3145                 } else {
3146                     /* Don't match patterns against characters in comment */
3147                     i++;
3148                     continue;
3149                 }
3150             }
3151             if (started == STARTED_CHATTER) {
3152                 if (buf[i] != '\n') {
3153                     /* Don't match patterns against characters in chatter */
3154                     i++;
3155                     continue;
3156                 }
3157                 started = STARTED_NONE;
3158                 if(suppressKibitz) next_out = i+1;
3159             }
3160
3161             /* Kludge to deal with rcmd protocol */
3162             if (firstTime && looking_at(buf, &i, "\001*")) {
3163                 DisplayFatalError(&buf[1], 0, 1);
3164                 continue;
3165             } else {
3166                 firstTime = FALSE;
3167             }
3168
3169             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3170                 ics_type = ICS_ICC;
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, "freechess.org")) {
3177                 ics_type = ICS_FICS;
3178                 ics_prefix = "$";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3184                 ics_type = ICS_CHESSNET;
3185                 ics_prefix = "/";
3186                 if (appData.debugMode)
3187                   fprintf(debugFP, "ics_type %d\n", ics_type);
3188                 continue;
3189             }
3190
3191             if (!loggedOn &&
3192                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3193                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3194                  looking_at(buf, &i, "will be \"*\""))) {
3195               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3196               continue;
3197             }
3198
3199             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3200               char buf[MSG_SIZ];
3201               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3202               DisplayIcsInteractionTitle(buf);
3203               have_set_title = TRUE;
3204             }
3205
3206             /* skip finger notes */
3207             if (started == STARTED_NONE &&
3208                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3209                  (buf[i] == '1' && buf[i+1] == '0')) &&
3210                 buf[i+2] == ':' && buf[i+3] == ' ') {
3211               started = STARTED_CHATTER;
3212               i += 3;
3213               continue;
3214             }
3215
3216             oldi = i;
3217             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3218             if(appData.seekGraph) {
3219                 if(soughtPending && MatchSoughtLine(buf+i)) {
3220                     i = strstr(buf+i, "rated") - buf;
3221                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                     next_out = leftover_start = i;
3223                     started = STARTED_CHATTER;
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3228                         && looking_at(buf, &i, "* ads displayed")) {
3229                     soughtPending = FALSE;
3230                     seekGraphUp = TRUE;
3231                     DrawSeekGraph();
3232                     continue;
3233                 }
3234                 if(appData.autoRefresh) {
3235                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3236                         int s = (ics_type == ICS_ICC); // ICC format differs
3237                         if(seekGraphUp)
3238                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3239                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3240                         looking_at(buf, &i, "*% "); // eat prompt
3241                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i; // suppress
3244                         continue;
3245                     }
3246                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3247                         char *p = star_match[0];
3248                         while(*p) {
3249                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3250                             while(*p && *p++ != ' '); // next
3251                         }
3252                         looking_at(buf, &i, "*% "); // eat prompt
3253                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3254                         next_out = i;
3255                         continue;
3256                     }
3257                 }
3258             }
3259
3260             /* skip formula vars */
3261             if (started == STARTED_NONE &&
3262                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3263               started = STARTED_CHATTER;
3264               i += 3;
3265               continue;
3266             }
3267
3268             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3269             if (appData.autoKibitz && started == STARTED_NONE &&
3270                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3271                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3272                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3273                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3274                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3275                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3276                         suppressKibitz = TRUE;
3277                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278                         next_out = i;
3279                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3280                                 && (gameMode == IcsPlayingWhite)) ||
3281                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3282                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3283                             started = STARTED_CHATTER; // own kibitz we simply discard
3284                         else {
3285                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3286                             parse_pos = 0; parse[0] = NULLCHAR;
3287                             savingComment = TRUE;
3288                             suppressKibitz = gameMode != IcsObserving ? 2 :
3289                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3290                         }
3291                         continue;
3292                 } else
3293                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3294                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3295                          && atoi(star_match[0])) {
3296                     // suppress the acknowledgements of our own autoKibitz
3297                     char *p;
3298                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3299                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3300                     SendToPlayer(star_match[0], strlen(star_match[0]));
3301                     if(looking_at(buf, &i, "*% ")) // eat prompt
3302                         suppressKibitz = FALSE;
3303                     next_out = i;
3304                     continue;
3305                 }
3306             } // [HGM] kibitz: end of patch
3307
3308             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3309
3310             // [HGM] chat: intercept tells by users for which we have an open chat window
3311             channel = -1;
3312             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3313                                            looking_at(buf, &i, "* whispers:") ||
3314                                            looking_at(buf, &i, "* kibitzes:") ||
3315                                            looking_at(buf, &i, "* shouts:") ||
3316                                            looking_at(buf, &i, "* c-shouts:") ||
3317                                            looking_at(buf, &i, "--> * ") ||
3318                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3321                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3322                 int p;
3323                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3324                 chattingPartner = -1; collective = 0;
3325
3326                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3327                 for(p=0; p<MAX_CHAT; p++) {
3328                     collective = 1;
3329                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3330                     talker[0] = '['; strcat(talker, "] ");
3331                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3332                     chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("kibitzes", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3344                 for(p=0; p<MAX_CHAT; p++) {
3345                     collective = 1;
3346                     if(!strcmp("whispers", chatPartner[p])) {
3347                         talker[0] = '['; strcat(talker, "] ");
3348                         chattingPartner = p; break;
3349                     }
3350                 } else
3351                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3352                   if(buf[i-8] == '-' && buf[i-3] == 't')
3353                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3354                     collective = 1;
3355                     if(!strcmp("c-shouts", chatPartner[p])) {
3356                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3357                         chattingPartner = p; break;
3358                     }
3359                   }
3360                   if(chattingPartner < 0)
3361                   for(p=0; p<MAX_CHAT; p++) {
3362                     collective = 1;
3363                     if(!strcmp("shouts", chatPartner[p])) {
3364                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3365                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3366                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3367                         chattingPartner = p; break;
3368                     }
3369                   }
3370                 }
3371                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3372                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3373                     talker[0] = 0;
3374                     Colorize(ColorTell, FALSE);
3375                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3376                     collective |= 2;
3377                     chattingPartner = p; break;
3378                 }
3379                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3380                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3381                     started = STARTED_COMMENT;
3382                     parse_pos = 0; parse[0] = NULLCHAR;
3383                     savingComment = 3 + chattingPartner; // counts as TRUE
3384                     if(collective == 3) i = oldi; else {
3385                         suppressKibitz = TRUE;
3386                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3387                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3388                         continue;
3389                     }
3390                 }
3391             } // [HGM] chat: end of patch
3392
3393           backup = i;
3394             if (appData.zippyTalk || appData.zippyPlay) {
3395                 /* [DM] Backup address for color zippy lines */
3396 #if ZIPPY
3397                if (loggedOn == TRUE)
3398                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3399                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3400                        ;
3401 #endif
3402             } // [DM] 'else { ' deleted
3403                 if (
3404                     /* Regular tells and says */
3405                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3406                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3407                     looking_at(buf, &i, "* says: ") ||
3408                     /* Don't color "message" or "messages" output */
3409                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3410                     looking_at(buf, &i, "*. * at *:*: ") ||
3411                     looking_at(buf, &i, "--* (*:*): ") ||
3412                     /* Message notifications (same color as tells) */
3413                     looking_at(buf, &i, "* has left a message ") ||
3414                     looking_at(buf, &i, "* just sent you a message:\n") ||
3415                     /* Whispers and kibitzes */
3416                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3417                     looking_at(buf, &i, "* kibitzes: ") ||
3418                     /* Channel tells */
3419                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3420
3421                   if (tkind == 1 && strchr(star_match[0], ':')) {
3422                       /* Avoid "tells you:" spoofs in channels */
3423                      tkind = 3;
3424                   }
3425                   if (star_match[0][0] == NULLCHAR ||
3426                       strchr(star_match[0], ' ') ||
3427                       (tkind == 3 && strchr(star_match[1], ' '))) {
3428                     /* Reject bogus matches */
3429                     i = oldi;
3430                   } else {
3431                     if (appData.colorize) {
3432                       if (oldi > next_out) {
3433                         SendToPlayer(&buf[next_out], oldi - next_out);
3434                         next_out = oldi;
3435                       }
3436                       switch (tkind) {
3437                       case 1:
3438                         Colorize(ColorTell, FALSE);
3439                         curColor = ColorTell;
3440                         break;
3441                       case 2:
3442                         Colorize(ColorKibitz, FALSE);
3443                         curColor = ColorKibitz;
3444                         break;
3445                       case 3:
3446                         p = strrchr(star_match[1], '(');
3447                         if (p == NULL) {
3448                           p = star_match[1];
3449                         } else {
3450                           p++;
3451                         }
3452                         if (atoi(p) == 1) {
3453                           Colorize(ColorChannel1, FALSE);
3454                           curColor = ColorChannel1;
3455                         } else {
3456                           Colorize(ColorChannel, FALSE);
3457                           curColor = ColorChannel;
3458                         }
3459                         break;
3460                       case 5:
3461                         curColor = ColorNormal;
3462                         break;
3463                       }
3464                     }
3465                     if (started == STARTED_NONE && appData.autoComment &&
3466                         (gameMode == IcsObserving ||
3467                          gameMode == IcsPlayingWhite ||
3468                          gameMode == IcsPlayingBlack)) {
3469                       parse_pos = i - oldi;
3470                       memcpy(parse, &buf[oldi], parse_pos);
3471                       parse[parse_pos] = NULLCHAR;
3472                       started = STARTED_COMMENT;
3473                       savingComment = TRUE;
3474                     } else if(collective != 3) {
3475                       started = STARTED_CHATTER;
3476                       savingComment = FALSE;
3477                     }
3478                     loggedOn = TRUE;
3479                     continue;
3480                   }
3481                 }
3482
3483                 if (looking_at(buf, &i, "* s-shouts: ") ||
3484                     looking_at(buf, &i, "* c-shouts: ")) {
3485                     if (appData.colorize) {
3486                         if (oldi > next_out) {
3487                             SendToPlayer(&buf[next_out], oldi - next_out);
3488                             next_out = oldi;
3489                         }
3490                         Colorize(ColorSShout, FALSE);
3491                         curColor = ColorSShout;
3492                     }
3493                     loggedOn = TRUE;
3494                     started = STARTED_CHATTER;
3495                     continue;
3496                 }
3497
3498                 if (looking_at(buf, &i, "--->")) {
3499                     loggedOn = TRUE;
3500                     continue;
3501                 }
3502
3503                 if (looking_at(buf, &i, "* shouts: ") ||
3504                     looking_at(buf, &i, "--> ")) {
3505                     if (appData.colorize) {
3506                         if (oldi > next_out) {
3507                             SendToPlayer(&buf[next_out], oldi - next_out);
3508                             next_out = oldi;
3509                         }
3510                         Colorize(ColorShout, FALSE);
3511                         curColor = ColorShout;
3512                     }
3513                     loggedOn = TRUE;
3514                     started = STARTED_CHATTER;
3515                     continue;
3516                 }
3517
3518                 if (looking_at( buf, &i, "Challenge:")) {
3519                     if (appData.colorize) {
3520                         if (oldi > next_out) {
3521                             SendToPlayer(&buf[next_out], oldi - next_out);
3522                             next_out = oldi;
3523                         }
3524                         Colorize(ColorChallenge, FALSE);
3525                         curColor = ColorChallenge;
3526                     }
3527                     loggedOn = TRUE;
3528                     continue;
3529                 }
3530
3531                 if (looking_at(buf, &i, "* offers you") ||
3532                     looking_at(buf, &i, "* offers to be") ||
3533                     looking_at(buf, &i, "* would like to") ||
3534                     looking_at(buf, &i, "* requests to") ||
3535                     looking_at(buf, &i, "Your opponent offers") ||
3536                     looking_at(buf, &i, "Your opponent requests")) {
3537
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorRequest, FALSE);
3544                         curColor = ColorRequest;
3545                     }
3546                     continue;
3547                 }
3548
3549                 if (looking_at(buf, &i, "* (*) seeking")) {
3550                     if (appData.colorize) {
3551                         if (oldi > next_out) {
3552                             SendToPlayer(&buf[next_out], oldi - next_out);
3553                             next_out = oldi;
3554                         }
3555                         Colorize(ColorSeek, FALSE);
3556                         curColor = ColorSeek;
3557                     }
3558                     continue;
3559             }
3560
3561           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3562
3563             if (looking_at(buf, &i, "\\   ")) {
3564                 if (prevColor != ColorNormal) {
3565                     if (oldi > next_out) {
3566                         SendToPlayer(&buf[next_out], oldi - next_out);
3567                         next_out = oldi;
3568                     }
3569                     Colorize(prevColor, TRUE);
3570                     curColor = prevColor;
3571                 }
3572                 if (savingComment) {
3573                     parse_pos = i - oldi;
3574                     memcpy(parse, &buf[oldi], parse_pos);
3575                     parse[parse_pos] = NULLCHAR;
3576                     started = STARTED_COMMENT;
3577                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3578                         chattingPartner = savingComment - 3; // kludge to remember the box
3579                 } else {
3580                     started = STARTED_CHATTER;
3581                 }
3582                 continue;
3583             }
3584
3585             if (looking_at(buf, &i, "Black Strength :") ||
3586                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3587                 looking_at(buf, &i, "<10>") ||
3588                 looking_at(buf, &i, "#@#")) {
3589                 /* Wrong board style */
3590                 loggedOn = TRUE;
3591                 SendToICS(ics_prefix);
3592                 SendToICS("set style 12\n");
3593                 SendToICS(ics_prefix);
3594                 SendToICS("refresh\n");
3595                 continue;
3596             }
3597
3598             if (looking_at(buf, &i, "login:")) {
3599               if (!have_sent_ICS_logon) {
3600                 if(ICSInitScript())
3601                   have_sent_ICS_logon = 1;
3602                 else // no init script was found
3603                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3604               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3605                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3606               }
3607                 continue;
3608             }
3609
3610             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3611                 (looking_at(buf, &i, "\n<12> ") ||
3612                  looking_at(buf, &i, "<12> "))) {
3613                 loggedOn = TRUE;
3614                 if (oldi > next_out) {
3615                     SendToPlayer(&buf[next_out], oldi - next_out);
3616                 }
3617                 next_out = i;
3618                 started = STARTED_BOARD;
3619                 parse_pos = 0;
3620                 continue;
3621             }
3622
3623             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3624                 looking_at(buf, &i, "<b1> ")) {
3625                 if (oldi > next_out) {
3626                     SendToPlayer(&buf[next_out], oldi - next_out);
3627                 }
3628                 next_out = i;
3629                 started = STARTED_HOLDINGS;
3630                 parse_pos = 0;
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3635                 loggedOn = TRUE;
3636                 /* Header for a move list -- first line */
3637
3638                 switch (ics_getting_history) {
3639                   case H_FALSE:
3640                     switch (gameMode) {
3641                       case IcsIdle:
3642                       case BeginningOfGame:
3643                         /* User typed "moves" or "oldmoves" while we
3644                            were idle.  Pretend we asked for these
3645                            moves and soak them up so user can step
3646                            through them and/or save them.
3647                            */
3648                         Reset(FALSE, TRUE);
3649                         gameMode = IcsObserving;
3650                         ModeHighlight();
3651                         ics_gamenum = -1;
3652                         ics_getting_history = H_GOT_UNREQ_HEADER;
3653                         break;
3654                       case EditGame: /*?*/
3655                       case EditPosition: /*?*/
3656                         /* Should above feature work in these modes too? */
3657                         /* For now it doesn't */
3658                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3659                         break;
3660                       default:
3661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3662                         break;
3663                     }
3664                     break;
3665                   case H_REQUESTED:
3666                     /* Is this the right one? */
3667                     if (gameInfo.white && gameInfo.black &&
3668                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3669                         strcmp(gameInfo.black, star_match[2]) == 0) {
3670                         /* All is well */
3671                         ics_getting_history = H_GOT_REQ_HEADER;
3672                     }
3673                     break;
3674                   case H_GOT_REQ_HEADER:
3675                   case H_GOT_UNREQ_HEADER:
3676                   case H_GOT_UNWANTED_HEADER:
3677                   case H_GETTING_MOVES:
3678                     /* Should not happen */
3679                     DisplayError(_("Error gathering move list: two headers"), 0);
3680                     ics_getting_history = H_FALSE;
3681                     break;
3682                 }
3683
3684                 /* Save player ratings into gameInfo if needed */
3685                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3686                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3687                     (gameInfo.whiteRating == -1 ||
3688                      gameInfo.blackRating == -1)) {
3689
3690                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3691                     gameInfo.blackRating = string_to_rating(star_match[3]);
3692                     if (appData.debugMode)
3693                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3694                               gameInfo.whiteRating, gameInfo.blackRating);
3695                 }
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i,
3700               "* * match, initial time: * minute*, increment: * second")) {
3701                 /* Header for a move list -- second line */
3702                 /* Initial board will follow if this is a wild game */
3703                 if (gameInfo.event != NULL) free(gameInfo.event);
3704                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3705                 gameInfo.event = StrSave(str);
3706                 /* [HGM] we switched variant. Translate boards if needed. */
3707                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3708                 continue;
3709             }
3710
3711             if (looking_at(buf, &i, "Move  ")) {
3712                 /* Beginning of a move list */
3713                 switch (ics_getting_history) {
3714                   case H_FALSE:
3715                     /* Normally should not happen */
3716                     /* Maybe user hit reset while we were parsing */
3717                     break;
3718                   case H_REQUESTED:
3719                     /* Happens if we are ignoring a move list that is not
3720                      * the one we just requested.  Common if the user
3721                      * tries to observe two games without turning off
3722                      * getMoveList */
3723                     break;
3724                   case H_GETTING_MOVES:
3725                     /* Should not happen */
3726                     DisplayError(_("Error gathering move list: nested"), 0);
3727                     ics_getting_history = H_FALSE;
3728                     break;
3729                   case H_GOT_REQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES;
3732                     parse_pos = 0;
3733                     if (oldi > next_out) {
3734                         SendToPlayer(&buf[next_out], oldi - next_out);
3735                     }
3736                     break;
3737                   case H_GOT_UNREQ_HEADER:
3738                     ics_getting_history = H_GETTING_MOVES;
3739                     started = STARTED_MOVES_NOHIDE;
3740                     parse_pos = 0;
3741                     break;
3742                   case H_GOT_UNWANTED_HEADER:
3743                     ics_getting_history = H_FALSE;
3744                     break;
3745                 }
3746                 continue;
3747             }
3748
3749             if (looking_at(buf, &i, "% ") ||
3750                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3751                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3752                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3753                     soughtPending = FALSE;
3754                     seekGraphUp = TRUE;
3755                     DrawSeekGraph();
3756                 }
3757                 if(suppressKibitz) next_out = i;
3758                 savingComment = FALSE;
3759                 suppressKibitz = 0;
3760                 switch (started) {
3761                   case STARTED_MOVES:
3762                   case STARTED_MOVES_NOHIDE:
3763                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3764                     parse[parse_pos + i - oldi] = NULLCHAR;
3765                     ParseGameHistory(parse);
3766 #if ZIPPY
3767                     if (appData.zippyPlay && first.initDone) {
3768                         FeedMovesToProgram(&first, forwardMostMove);
3769                         if (gameMode == IcsPlayingWhite) {
3770                             if (WhiteOnMove(forwardMostMove)) {
3771                                 if (first.sendTime) {
3772                                   if (first.useColors) {
3773                                     SendToProgram("black\n", &first);
3774                                   }
3775                                   SendTimeRemaining(&first, TRUE);
3776                                 }
3777                                 if (first.useColors) {
3778                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3779                                 }
3780                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3781                                 first.maybeThinking = TRUE;
3782                             } else {
3783                                 if (first.usePlayother) {
3784                                   if (first.sendTime) {
3785                                     SendTimeRemaining(&first, TRUE);
3786                                   }
3787                                   SendToProgram("playother\n", &first);
3788                                   firstMove = FALSE;
3789                                 } else {
3790                                   firstMove = TRUE;
3791                                 }
3792                             }
3793                         } else if (gameMode == IcsPlayingBlack) {
3794                             if (!WhiteOnMove(forwardMostMove)) {
3795                                 if (first.sendTime) {
3796                                   if (first.useColors) {
3797                                     SendToProgram("white\n", &first);
3798                                   }
3799                                   SendTimeRemaining(&first, FALSE);
3800                                 }
3801                                 if (first.useColors) {
3802                                   SendToProgram("black\n", &first);
3803                                 }
3804                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3805                                 first.maybeThinking = TRUE;
3806                             } else {
3807                                 if (first.usePlayother) {
3808                                   if (first.sendTime) {
3809                                     SendTimeRemaining(&first, FALSE);
3810                                   }
3811                                   SendToProgram("playother\n", &first);
3812                                   firstMove = FALSE;
3813                                 } else {
3814                                   firstMove = TRUE;
3815                                 }
3816                             }
3817                         }
3818                     }
3819 #endif
3820                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3821                         /* Moves came from oldmoves or moves command
3822                            while we weren't doing anything else.
3823                            */
3824                         currentMove = forwardMostMove;
3825                         ClearHighlights();/*!!could figure this out*/
3826                         flipView = appData.flipView;
3827                         DrawPosition(TRUE, boards[currentMove]);
3828                         DisplayBothClocks();
3829                         snprintf(str, MSG_SIZ, "%s %s %s",
3830                                 gameInfo.white, _("vs."),  gameInfo.black);
3831                         DisplayTitle(str);
3832                         gameMode = IcsIdle;
3833                     } else {
3834                         /* Moves were history of an active game */
3835                         if (gameInfo.resultDetails != NULL) {
3836                             free(gameInfo.resultDetails);
3837                             gameInfo.resultDetails = NULL;
3838                         }
3839                     }
3840                     HistorySet(parseList, backwardMostMove,
3841                                forwardMostMove, currentMove-1);
3842                     DisplayMove(currentMove - 1);
3843                     if (started == STARTED_MOVES) next_out = i;
3844                     started = STARTED_NONE;
3845                     ics_getting_history = H_FALSE;
3846                     break;
3847
3848                   case STARTED_OBSERVE:
3849                     started = STARTED_NONE;
3850                     SendToICS(ics_prefix);
3851                     SendToICS("refresh\n");
3852                     break;
3853
3854                   default:
3855                     break;
3856                 }
3857                 if(bookHit) { // [HGM] book: simulate book reply
3858                     static char bookMove[MSG_SIZ]; // a bit generous?
3859
3860                     programStats.nodes = programStats.depth = programStats.time =
3861                     programStats.score = programStats.got_only_move = 0;
3862                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3863
3864                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3865                     strcat(bookMove, bookHit);
3866                     HandleMachineMove(bookMove, &first);
3867                 }
3868                 continue;
3869             }
3870
3871             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3872                  started == STARTED_HOLDINGS ||
3873                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3874                 /* Accumulate characters in move list or board */
3875                 parse[parse_pos++] = buf[i];
3876             }
3877
3878             /* Start of game messages.  Mostly we detect start of game
3879                when the first board image arrives.  On some versions
3880                of the ICS, though, we need to do a "refresh" after starting
3881                to observe in order to get the current board right away. */
3882             if (looking_at(buf, &i, "Adding game * to observation list")) {
3883                 started = STARTED_OBSERVE;
3884                 continue;
3885             }
3886
3887             /* Handle auto-observe */
3888             if (appData.autoObserve &&
3889                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3890                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3891                 char *player;
3892                 /* Choose the player that was highlighted, if any. */
3893                 if (star_match[0][0] == '\033' ||
3894                     star_match[1][0] != '\033') {
3895                     player = star_match[0];
3896                 } else {
3897                     player = star_match[2];
3898                 }
3899                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3900                         ics_prefix, StripHighlightAndTitle(player));
3901                 SendToICS(str);
3902
3903                 /* Save ratings from notify string */
3904                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3905                 player1Rating = string_to_rating(star_match[1]);
3906                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3907                 player2Rating = string_to_rating(star_match[3]);
3908
3909                 if (appData.debugMode)
3910                   fprintf(debugFP,
3911                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3912                           player1Name, player1Rating,
3913                           player2Name, player2Rating);
3914
3915                 continue;
3916             }
3917
3918             /* Deal with automatic examine mode after a game,
3919                and with IcsObserving -> IcsExamining transition */
3920             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3921                 looking_at(buf, &i, "has made you an examiner of game *")) {
3922
3923                 int gamenum = atoi(star_match[0]);
3924                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3925                     gamenum == ics_gamenum) {
3926                     /* We were already playing or observing this game;
3927                        no need to refetch history */
3928                     gameMode = IcsExamining;
3929                     if (pausing) {
3930                         pauseExamForwardMostMove = forwardMostMove;
3931                     } else if (currentMove < forwardMostMove) {
3932                         ForwardInner(forwardMostMove);
3933                     }
3934                 } else {
3935                     /* I don't think this case really can happen */
3936                     SendToICS(ics_prefix);
3937                     SendToICS("refresh\n");
3938                 }
3939                 continue;
3940             }
3941
3942             /* Error messages */
3943 //          if (ics_user_moved) {
3944             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3945                 if (looking_at(buf, &i, "Illegal move") ||
3946                     looking_at(buf, &i, "Not a legal move") ||
3947                     looking_at(buf, &i, "Your king is in check") ||
3948                     looking_at(buf, &i, "It isn't your turn") ||
3949                     looking_at(buf, &i, "It is not your move")) {
3950                     /* Illegal move */
3951                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3952                         currentMove = forwardMostMove-1;
3953                         DisplayMove(currentMove - 1); /* before DMError */
3954                         DrawPosition(FALSE, boards[currentMove]);
3955                         SwitchClocks(forwardMostMove-1); // [HGM] race
3956                         DisplayBothClocks();
3957                     }
3958                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3959                     ics_user_moved = 0;
3960                     continue;
3961                 }
3962             }
3963
3964             if (looking_at(buf, &i, "still have time") ||
3965                 looking_at(buf, &i, "not out of time") ||
3966                 looking_at(buf, &i, "either player is out of time") ||
3967                 looking_at(buf, &i, "has timeseal; checking")) {
3968                 /* We must have called his flag a little too soon */
3969                 whiteFlag = blackFlag = FALSE;
3970                 continue;
3971             }
3972
3973             if (looking_at(buf, &i, "added * seconds to") ||
3974                 looking_at(buf, &i, "seconds were added to")) {
3975                 /* Update the clocks */
3976                 SendToICS(ics_prefix);
3977                 SendToICS("refresh\n");
3978                 continue;
3979             }
3980
3981             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3982                 ics_clock_paused = TRUE;
3983                 StopClocks();
3984                 continue;
3985             }
3986
3987             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3988                 ics_clock_paused = FALSE;
3989                 StartClocks();
3990                 continue;
3991             }
3992
3993             /* Grab player ratings from the Creating: message.
3994                Note we have to check for the special case when
3995                the ICS inserts things like [white] or [black]. */
3996             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3997                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3998                 /* star_matches:
3999                    0    player 1 name (not necessarily white)
4000                    1    player 1 rating
4001                    2    empty, white, or black (IGNORED)
4002                    3    player 2 name (not necessarily black)
4003                    4    player 2 rating
4004
4005                    The names/ratings are sorted out when the game
4006                    actually starts (below).
4007                 */
4008                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4009                 player1Rating = string_to_rating(star_match[1]);
4010                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4011                 player2Rating = string_to_rating(star_match[4]);
4012
4013                 if (appData.debugMode)
4014                   fprintf(debugFP,
4015                           "Ratings from 'Creating:' %s %d, %s %d\n",
4016                           player1Name, player1Rating,
4017                           player2Name, player2Rating);
4018
4019                 continue;
4020             }
4021
4022             /* Improved generic start/end-of-game messages */
4023             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4024                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4025                 /* If tkind == 0: */
4026                 /* star_match[0] is the game number */
4027                 /*           [1] is the white player's name */
4028                 /*           [2] is the black player's name */
4029                 /* For end-of-game: */
4030                 /*           [3] is the reason for the game end */
4031                 /*           [4] is a PGN end game-token, preceded by " " */
4032                 /* For start-of-game: */
4033                 /*           [3] begins with "Creating" or "Continuing" */
4034                 /*           [4] is " *" or empty (don't care). */
4035                 int gamenum = atoi(star_match[0]);
4036                 char *whitename, *blackname, *why, *endtoken;
4037                 ChessMove endtype = EndOfFile;
4038
4039                 if (tkind == 0) {
4040                   whitename = star_match[1];
4041                   blackname = star_match[2];
4042                   why = star_match[3];
4043                   endtoken = star_match[4];
4044                 } else {
4045                   whitename = star_match[1];
4046                   blackname = star_match[3];
4047                   why = star_match[5];
4048                   endtoken = star_match[6];
4049                 }
4050
4051                 /* Game start messages */
4052                 if (strncmp(why, "Creating ", 9) == 0 ||
4053                     strncmp(why, "Continuing ", 11) == 0) {
4054                     gs_gamenum = gamenum;
4055                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4056                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4057                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4058 #if ZIPPY
4059                     if (appData.zippyPlay) {
4060                         ZippyGameStart(whitename, blackname);
4061                     }
4062 #endif /*ZIPPY*/
4063                     partnerBoardValid = FALSE; // [HGM] bughouse
4064                     continue;
4065                 }
4066
4067                 /* Game end messages */
4068                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4069                     ics_gamenum != gamenum) {
4070                     continue;
4071                 }
4072                 while (endtoken[0] == ' ') endtoken++;
4073                 switch (endtoken[0]) {
4074                   case '*':
4075                   default:
4076                     endtype = GameUnfinished;
4077                     break;
4078                   case '0':
4079                     endtype = BlackWins;
4080                     break;
4081                   case '1':
4082                     if (endtoken[1] == '/')
4083                       endtype = GameIsDrawn;
4084                     else
4085                       endtype = WhiteWins;
4086                     break;
4087                 }
4088                 GameEnds(endtype, why, GE_ICS);
4089 #if ZIPPY
4090                 if (appData.zippyPlay && first.initDone) {
4091                     ZippyGameEnd(endtype, why);
4092                     if (first.pr == NoProc) {
4093                       /* Start the next process early so that we'll
4094                          be ready for the next challenge */
4095                       StartChessProgram(&first);
4096                     }
4097                     /* Send "new" early, in case this command takes
4098                        a long time to finish, so that we'll be ready
4099                        for the next challenge. */
4100                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4101                     Reset(TRUE, TRUE);
4102                 }
4103 #endif /*ZIPPY*/
4104                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4105                 continue;
4106             }
4107
4108             if (looking_at(buf, &i, "Removing game * from observation") ||
4109                 looking_at(buf, &i, "no longer observing game *") ||
4110                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4111                 if (gameMode == IcsObserving &&
4112                     atoi(star_match[0]) == ics_gamenum)
4113                   {
4114                       /* icsEngineAnalyze */
4115                       if (appData.icsEngineAnalyze) {
4116                             ExitAnalyzeMode();
4117                             ModeHighlight();
4118                       }
4119                       StopClocks();
4120                       gameMode = IcsIdle;
4121                       ics_gamenum = -1;
4122                       ics_user_moved = FALSE;
4123                   }
4124                 continue;
4125             }
4126
4127             if (looking_at(buf, &i, "no longer examining game *")) {
4128                 if (gameMode == IcsExamining &&
4129                     atoi(star_match[0]) == ics_gamenum)
4130                   {
4131                       gameMode = IcsIdle;
4132                       ics_gamenum = -1;
4133                       ics_user_moved = FALSE;
4134                   }
4135                 continue;
4136             }
4137
4138             /* Advance leftover_start past any newlines we find,
4139                so only partial lines can get reparsed */
4140             if (looking_at(buf, &i, "\n")) {
4141                 prevColor = curColor;
4142                 if (curColor != ColorNormal) {
4143                     if (oldi > next_out) {
4144                         SendToPlayer(&buf[next_out], oldi - next_out);
4145                         next_out = oldi;
4146                     }
4147                     Colorize(ColorNormal, FALSE);
4148                     curColor = ColorNormal;
4149                 }
4150                 if (started == STARTED_BOARD) {
4151                     started = STARTED_NONE;
4152                     parse[parse_pos] = NULLCHAR;
4153                     ParseBoard12(parse);
4154                     ics_user_moved = 0;
4155
4156                     /* Send premove here */
4157                     if (appData.premove) {
4158                       char str[MSG_SIZ];
4159                       if (currentMove == 0 &&
4160                           gameMode == IcsPlayingWhite &&
4161                           appData.premoveWhite) {
4162                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4163                         if (appData.debugMode)
4164                           fprintf(debugFP, "Sending premove:\n");
4165                         SendToICS(str);
4166                       } else if (currentMove == 1 &&
4167                                  gameMode == IcsPlayingBlack &&
4168                                  appData.premoveBlack) {
4169                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4170                         if (appData.debugMode)
4171                           fprintf(debugFP, "Sending premove:\n");
4172                         SendToICS(str);
4173                       } else if (gotPremove) {
4174                         int oldFMM = forwardMostMove;
4175                         gotPremove = 0;
4176                         ClearPremoveHighlights();
4177                         if (appData.debugMode)
4178                           fprintf(debugFP, "Sending premove:\n");
4179                           UserMoveEvent(premoveFromX, premoveFromY,
4180                                         premoveToX, premoveToY,
4181                                         premovePromoChar);
4182                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4183                           if(moveList[oldFMM-1][1] != '@')
4184                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4185                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4186                           else // (drop)
4187                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4188                         }
4189                       }
4190                     }
4191
4192                     /* Usually suppress following prompt */
4193                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4194                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4195                         if (looking_at(buf, &i, "*% ")) {
4196                             savingComment = FALSE;
4197                             suppressKibitz = 0;
4198                         }
4199                     }
4200                     next_out = i;
4201                 } else if (started == STARTED_HOLDINGS) {
4202                     int gamenum;
4203                     char new_piece[MSG_SIZ];
4204                     started = STARTED_NONE;
4205                     parse[parse_pos] = NULLCHAR;
4206                     if (appData.debugMode)
4207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4208                                                         parse, currentMove);
4209                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4210                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4211                         if (gameInfo.variant == VariantNormal) {
4212                           /* [HGM] We seem to switch variant during a game!
4213                            * Presumably no holdings were displayed, so we have
4214                            * to move the position two files to the right to
4215                            * create room for them!
4216                            */
4217                           VariantClass newVariant;
4218                           switch(gameInfo.boardWidth) { // base guess on board width
4219                                 case 9:  newVariant = VariantShogi; break;
4220                                 case 10: newVariant = VariantGreat; break;
4221                                 default: newVariant = VariantCrazyhouse; break;
4222                           }
4223                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4224                           /* Get a move list just to see the header, which
4225                              will tell us whether this is really bug or zh */
4226                           if (ics_getting_history == H_FALSE) {
4227                             ics_getting_history = H_REQUESTED;
4228                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229                             SendToICS(str);
4230                           }
4231                         }
4232                         new_piece[0] = NULLCHAR;
4233                         sscanf(parse, "game %d white [%s black [%s <- %s",
4234                                &gamenum, white_holding, black_holding,
4235                                new_piece);
4236                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4237                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4238                         /* [HGM] copy holdings to board holdings area */
4239                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4240                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4241                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4242 #if ZIPPY
4243                         if (appData.zippyPlay && first.initDone) {
4244                             ZippyHoldings(white_holding, black_holding,
4245                                           new_piece);
4246                         }
4247 #endif /*ZIPPY*/
4248                         if (tinyLayout || smallLayout) {
4249                             char wh[16], bh[16];
4250                             PackHolding(wh, white_holding);
4251                             PackHolding(bh, black_holding);
4252                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4253                                     gameInfo.white, gameInfo.black);
4254                         } else {
4255                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4256                                     gameInfo.white, white_holding, _("vs."),
4257                                     gameInfo.black, black_holding);
4258                         }
4259                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4260                         DrawPosition(FALSE, boards[currentMove]);
4261                         DisplayTitle(str);
4262                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4263                         sscanf(parse, "game %d white [%s black [%s <- %s",
4264                                &gamenum, white_holding, black_holding,
4265                                new_piece);
4266                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4267                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4268                         /* [HGM] copy holdings to partner-board holdings area */
4269                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4270                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4271                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4272                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4273                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4274                       }
4275                     }
4276                     /* Suppress following prompt */
4277                     if (looking_at(buf, &i, "*% ")) {
4278                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4279                         savingComment = FALSE;
4280                         suppressKibitz = 0;
4281                     }
4282                     next_out = i;
4283                 }
4284                 continue;
4285             }
4286
4287             i++;                /* skip unparsed character and loop back */
4288         }
4289
4290         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4291 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4292 //          SendToPlayer(&buf[next_out], i - next_out);
4293             started != STARTED_HOLDINGS && leftover_start > next_out) {
4294             SendToPlayer(&buf[next_out], leftover_start - next_out);
4295             next_out = i;
4296         }
4297
4298         leftover_len = buf_len - leftover_start;
4299         /* if buffer ends with something we couldn't parse,
4300            reparse it after appending the next read */
4301
4302     } else if (count == 0) {
4303         RemoveInputSource(isr);
4304         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4305     } else {
4306         DisplayFatalError(_("Error reading from ICS"), error, 1);
4307     }
4308 }
4309
4310
4311 /* Board style 12 looks like this:
4312
4313    <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
4314
4315  * The "<12> " is stripped before it gets to this routine.  The two
4316  * trailing 0's (flip state and clock ticking) are later addition, and
4317  * some chess servers may not have them, or may have only the first.
4318  * Additional trailing fields may be added in the future.
4319  */
4320
4321 #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"
4322
4323 #define RELATION_OBSERVING_PLAYED    0
4324 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4325 #define RELATION_PLAYING_MYMOVE      1
4326 #define RELATION_PLAYING_NOTMYMOVE  -1
4327 #define RELATION_EXAMINING           2
4328 #define RELATION_ISOLATED_BOARD     -3
4329 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4330
4331 void
4332 ParseBoard12 (char *string)
4333 {
4334 #if ZIPPY
4335     int i, takeback;
4336     char *bookHit = NULL; // [HGM] book
4337 #endif
4338     GameMode newGameMode;
4339     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4340     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4341     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4342     char to_play, board_chars[200];
4343     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4344     char black[32], white[32];
4345     Board board;
4346     int prevMove = currentMove;
4347     int ticking = 2;
4348     ChessMove moveType;
4349     int fromX, fromY, toX, toY;
4350     char promoChar;
4351     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4352     Boolean weird = FALSE, reqFlag = FALSE;
4353
4354     fromX = fromY = toX = toY = -1;
4355
4356     newGame = FALSE;
4357
4358     if (appData.debugMode)
4359       fprintf(debugFP, "Parsing board: %s\n", string);
4360
4361     move_str[0] = NULLCHAR;
4362     elapsed_time[0] = NULLCHAR;
4363     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4364         int  i = 0, j;
4365         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4366             if(string[i] == ' ') { ranks++; files = 0; }
4367             else files++;
4368             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4369             i++;
4370         }
4371         for(j = 0; j <i; j++) board_chars[j] = string[j];
4372         board_chars[i] = '\0';
4373         string += i + 1;
4374     }
4375     n = sscanf(string, PATTERN, &to_play, &double_push,
4376                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4377                &gamenum, white, black, &relation, &basetime, &increment,
4378                &white_stren, &black_stren, &white_time, &black_time,
4379                &moveNum, str, elapsed_time, move_str, &ics_flip,
4380                &ticking);
4381
4382     if (n < 21) {
4383         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4384         DisplayError(str, 0);
4385         return;
4386     }
4387
4388     /* Convert the move number to internal form */
4389     moveNum = (moveNum - 1) * 2;
4390     if (to_play == 'B') moveNum++;
4391     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4392       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4393                         0, 1);
4394       return;
4395     }
4396
4397     switch (relation) {
4398       case RELATION_OBSERVING_PLAYED:
4399       case RELATION_OBSERVING_STATIC:
4400         if (gamenum == -1) {
4401             /* Old ICC buglet */
4402             relation = RELATION_OBSERVING_STATIC;
4403         }
4404         newGameMode = IcsObserving;
4405         break;
4406       case RELATION_PLAYING_MYMOVE:
4407       case RELATION_PLAYING_NOTMYMOVE:
4408         newGameMode =
4409           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4410             IcsPlayingWhite : IcsPlayingBlack;
4411         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4412         break;
4413       case RELATION_EXAMINING:
4414         newGameMode = IcsExamining;
4415         break;
4416       case RELATION_ISOLATED_BOARD:
4417       default:
4418         /* Just display this board.  If user was doing something else,
4419            we will forget about it until the next board comes. */
4420         newGameMode = IcsIdle;
4421         break;
4422       case RELATION_STARTING_POSITION:
4423         newGameMode = gameMode;
4424         break;
4425     }
4426
4427     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4428         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4429          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4430       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4431       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4432       static int lastBgGame = -1;
4433       char *toSqr;
4434       for (k = 0; k < ranks; k++) {
4435         for (j = 0; j < files; j++)
4436           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437         if(gameInfo.holdingsWidth > 1) {
4438              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440         }
4441       }
4442       CopyBoard(partnerBoard, board);
4443       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4444         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4445         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4446       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4447       if(toSqr = strchr(str, '-')) {
4448         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4449         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4450       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4451       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4452       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4453       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4454       if(twoBoards) {
4455           DisplayWhiteClock(white_time*fac, to_play == 'W');
4456           DisplayBlackClock(black_time*fac, to_play != 'W');
4457           activePartner = to_play;
4458           if(gamenum != lastBgGame) {
4459               char buf[MSG_SIZ];
4460               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4461               DisplayTitle(buf);
4462           }
4463           lastBgGame = gamenum;
4464           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4465                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4466       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4467                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4468       if(!twoBoards) DisplayMessage(partnerStatus, "");
4469         partnerBoardValid = TRUE;
4470       return;
4471     }
4472
4473     if(appData.dualBoard && appData.bgObserve) {
4474         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4475             SendToICS(ics_prefix), SendToICS("pobserve\n");
4476         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4477             char buf[MSG_SIZ];
4478             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4479             SendToICS(buf);
4480         }
4481     }
4482
4483     /* Modify behavior for initial board display on move listing
4484        of wild games.
4485        */
4486     switch (ics_getting_history) {
4487       case H_FALSE:
4488       case H_REQUESTED:
4489         break;
4490       case H_GOT_REQ_HEADER:
4491       case H_GOT_UNREQ_HEADER:
4492         /* This is the initial position of the current game */
4493         gamenum = ics_gamenum;
4494         moveNum = 0;            /* old ICS bug workaround */
4495         if (to_play == 'B') {
4496           startedFromSetupPosition = TRUE;
4497           blackPlaysFirst = TRUE;
4498           moveNum = 1;
4499           if (forwardMostMove == 0) forwardMostMove = 1;
4500           if (backwardMostMove == 0) backwardMostMove = 1;
4501           if (currentMove == 0) currentMove = 1;
4502         }
4503         newGameMode = gameMode;
4504         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4505         break;
4506       case H_GOT_UNWANTED_HEADER:
4507         /* This is an initial board that we don't want */
4508         return;
4509       case H_GETTING_MOVES:
4510         /* Should not happen */
4511         DisplayError(_("Error gathering move list: extra board"), 0);
4512         ics_getting_history = H_FALSE;
4513         return;
4514     }
4515
4516    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4517                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4518                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4519      /* [HGM] We seem to have switched variant unexpectedly
4520       * Try to guess new variant from board size
4521       */
4522           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4523           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4524           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4525           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4526           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4527           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4528           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4529           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4530           /* Get a move list just to see the header, which
4531              will tell us whether this is really bug or zh */
4532           if (ics_getting_history == H_FALSE) {
4533             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4534             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4535             SendToICS(str);
4536           }
4537     }
4538
4539     /* Take action if this is the first board of a new game, or of a
4540        different game than is currently being displayed.  */
4541     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4542         relation == RELATION_ISOLATED_BOARD) {
4543
4544         /* Forget the old game and get the history (if any) of the new one */
4545         if (gameMode != BeginningOfGame) {
4546           Reset(TRUE, TRUE);
4547         }
4548         newGame = TRUE;
4549         if (appData.autoRaiseBoard) BoardToTop();
4550         prevMove = -3;
4551         if (gamenum == -1) {
4552             newGameMode = IcsIdle;
4553         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4554                    appData.getMoveList && !reqFlag) {
4555             /* Need to get game history */
4556             ics_getting_history = H_REQUESTED;
4557             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4558             SendToICS(str);
4559         }
4560
4561         /* Initially flip the board to have black on the bottom if playing
4562            black or if the ICS flip flag is set, but let the user change
4563            it with the Flip View button. */
4564         flipView = appData.autoFlipView ?
4565           (newGameMode == IcsPlayingBlack) || ics_flip :
4566           appData.flipView;
4567
4568         /* Done with values from previous mode; copy in new ones */
4569         gameMode = newGameMode;
4570         ModeHighlight();
4571         ics_gamenum = gamenum;
4572         if (gamenum == gs_gamenum) {
4573             int klen = strlen(gs_kind);
4574             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4575             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4576             gameInfo.event = StrSave(str);
4577         } else {
4578             gameInfo.event = StrSave("ICS game");
4579         }
4580         gameInfo.site = StrSave(appData.icsHost);
4581         gameInfo.date = PGNDate();
4582         gameInfo.round = StrSave("-");
4583         gameInfo.white = StrSave(white);
4584         gameInfo.black = StrSave(black);
4585         timeControl = basetime * 60 * 1000;
4586         timeControl_2 = 0;
4587         timeIncrement = increment * 1000;
4588         movesPerSession = 0;
4589         gameInfo.timeControl = TimeControlTagValue();
4590         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4591   if (appData.debugMode) {
4592     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4593     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4594     setbuf(debugFP, NULL);
4595   }
4596
4597         gameInfo.outOfBook = NULL;
4598
4599         /* Do we have the ratings? */
4600         if (strcmp(player1Name, white) == 0 &&
4601             strcmp(player2Name, black) == 0) {
4602             if (appData.debugMode)
4603               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4604                       player1Rating, player2Rating);
4605             gameInfo.whiteRating = player1Rating;
4606             gameInfo.blackRating = player2Rating;
4607         } else if (strcmp(player2Name, white) == 0 &&
4608                    strcmp(player1Name, black) == 0) {
4609             if (appData.debugMode)
4610               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4611                       player2Rating, player1Rating);
4612             gameInfo.whiteRating = player2Rating;
4613             gameInfo.blackRating = player1Rating;
4614         }
4615         player1Name[0] = player2Name[0] = NULLCHAR;
4616
4617         /* Silence shouts if requested */
4618         if (appData.quietPlay &&
4619             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4620             SendToICS(ics_prefix);
4621             SendToICS("set shout 0\n");
4622         }
4623     }
4624
4625     /* Deal with midgame name changes */
4626     if (!newGame) {
4627         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4628             if (gameInfo.white) free(gameInfo.white);
4629             gameInfo.white = StrSave(white);
4630         }
4631         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4632             if (gameInfo.black) free(gameInfo.black);
4633             gameInfo.black = StrSave(black);
4634         }
4635     }
4636
4637     /* Throw away game result if anything actually changes in examine mode */
4638     if (gameMode == IcsExamining && !newGame) {
4639         gameInfo.result = GameUnfinished;
4640         if (gameInfo.resultDetails != NULL) {
4641             free(gameInfo.resultDetails);
4642             gameInfo.resultDetails = NULL;
4643         }
4644     }
4645
4646     /* In pausing && IcsExamining mode, we ignore boards coming
4647        in if they are in a different variation than we are. */
4648     if (pauseExamInvalid) return;
4649     if (pausing && gameMode == IcsExamining) {
4650         if (moveNum <= pauseExamForwardMostMove) {
4651             pauseExamInvalid = TRUE;
4652             forwardMostMove = pauseExamForwardMostMove;
4653             return;
4654         }
4655     }
4656
4657   if (appData.debugMode) {
4658     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4659   }
4660     /* Parse the board */
4661     for (k = 0; k < ranks; k++) {
4662       for (j = 0; j < files; j++)
4663         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4664       if(gameInfo.holdingsWidth > 1) {
4665            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4666            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4667       }
4668     }
4669     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4670       board[5][BOARD_RGHT+1] = WhiteAngel;
4671       board[6][BOARD_RGHT+1] = WhiteMarshall;
4672       board[1][0] = BlackMarshall;
4673       board[2][0] = BlackAngel;
4674       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4675     }
4676     CopyBoard(boards[moveNum], board);
4677     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4678     if (moveNum == 0) {
4679         startedFromSetupPosition =
4680           !CompareBoards(board, initialPosition);
4681         if(startedFromSetupPosition)
4682             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4683     }
4684
4685     /* [HGM] Set castling rights. Take the outermost Rooks,
4686        to make it also work for FRC opening positions. Note that board12
4687        is really defective for later FRC positions, as it has no way to
4688        indicate which Rook can castle if they are on the same side of King.
4689        For the initial position we grant rights to the outermost Rooks,
4690        and remember thos rights, and we then copy them on positions
4691        later in an FRC game. This means WB might not recognize castlings with
4692        Rooks that have moved back to their original position as illegal,
4693        but in ICS mode that is not its job anyway.
4694     */
4695     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4696     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4697
4698         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4699             if(board[0][i] == WhiteRook) j = i;
4700         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4702             if(board[0][i] == WhiteRook) j = i;
4703         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4704         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4706         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4707         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4708             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4709         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4710
4711         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4712         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4714             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4715         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4716             if(board[BOARD_HEIGHT-1][k] == bKing)
4717                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4718         if(gameInfo.variant == VariantTwoKings) {
4719             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4720             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4721             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4722         }
4723     } else { int r;
4724         r = boards[moveNum][CASTLING][0] = initialRights[0];
4725         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4726         r = boards[moveNum][CASTLING][1] = initialRights[1];
4727         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4728         r = boards[moveNum][CASTLING][3] = initialRights[3];
4729         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4730         r = boards[moveNum][CASTLING][4] = initialRights[4];
4731         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4732         /* wildcastle kludge: always assume King has rights */
4733         r = boards[moveNum][CASTLING][2] = initialRights[2];
4734         r = boards[moveNum][CASTLING][5] = initialRights[5];
4735     }
4736     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4737     boards[moveNum][EP_STATUS] = EP_NONE;
4738     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4739     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4740     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4741
4742
4743     if (ics_getting_history == H_GOT_REQ_HEADER ||
4744         ics_getting_history == H_GOT_UNREQ_HEADER) {
4745         /* This was an initial position from a move list, not
4746            the current position */
4747         return;
4748     }
4749
4750     /* Update currentMove and known move number limits */
4751     newMove = newGame || moveNum > forwardMostMove;
4752
4753     if (newGame) {
4754         forwardMostMove = backwardMostMove = currentMove = moveNum;
4755         if (gameMode == IcsExamining && moveNum == 0) {
4756           /* Workaround for ICS limitation: we are not told the wild
4757              type when starting to examine a game.  But if we ask for
4758              the move list, the move list header will tell us */
4759             ics_getting_history = H_REQUESTED;
4760             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4761             SendToICS(str);
4762         }
4763     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4764                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4765 #if ZIPPY
4766         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4767         /* [HGM] applied this also to an engine that is silently watching        */
4768         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4769             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4770             gameInfo.variant == currentlyInitializedVariant) {
4771           takeback = forwardMostMove - moveNum;
4772           for (i = 0; i < takeback; i++) {
4773             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4774             SendToProgram("undo\n", &first);
4775           }
4776         }
4777 #endif
4778
4779         forwardMostMove = moveNum;
4780         if (!pausing || currentMove > forwardMostMove)
4781           currentMove = forwardMostMove;
4782     } else {
4783         /* New part of history that is not contiguous with old part */
4784         if (pausing && gameMode == IcsExamining) {
4785             pauseExamInvalid = TRUE;
4786             forwardMostMove = pauseExamForwardMostMove;
4787             return;
4788         }
4789         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4790 #if ZIPPY
4791             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4792                 // [HGM] when we will receive the move list we now request, it will be
4793                 // fed to the engine from the first move on. So if the engine is not
4794                 // in the initial position now, bring it there.
4795                 InitChessProgram(&first, 0);
4796             }
4797 #endif
4798             ics_getting_history = H_REQUESTED;
4799             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4800             SendToICS(str);
4801         }
4802         forwardMostMove = backwardMostMove = currentMove = moveNum;
4803     }
4804
4805     /* Update the clocks */
4806     if (strchr(elapsed_time, '.')) {
4807       /* Time is in ms */
4808       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4809       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4810     } else {
4811       /* Time is in seconds */
4812       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4813       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4814     }
4815
4816
4817 #if ZIPPY
4818     if (appData.zippyPlay && newGame &&
4819         gameMode != IcsObserving && gameMode != IcsIdle &&
4820         gameMode != IcsExamining)
4821       ZippyFirstBoard(moveNum, basetime, increment);
4822 #endif
4823
4824     /* Put the move on the move list, first converting
4825        to canonical algebraic form. */
4826     if (moveNum > 0) {
4827   if (appData.debugMode) {
4828     int f = forwardMostMove;
4829     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4830             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4831             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4832     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4833     fprintf(debugFP, "moveNum = %d\n", moveNum);
4834     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4835     setbuf(debugFP, NULL);
4836   }
4837         if (moveNum <= backwardMostMove) {
4838             /* We don't know what the board looked like before
4839                this move.  Punt. */
4840           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4841             strcat(parseList[moveNum - 1], " ");
4842             strcat(parseList[moveNum - 1], elapsed_time);
4843             moveList[moveNum - 1][0] = NULLCHAR;
4844         } else if (strcmp(move_str, "none") == 0) {
4845             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4846             /* Again, we don't know what the board looked like;
4847                this is really the start of the game. */
4848             parseList[moveNum - 1][0] = NULLCHAR;
4849             moveList[moveNum - 1][0] = NULLCHAR;
4850             backwardMostMove = moveNum;
4851             startedFromSetupPosition = TRUE;
4852             fromX = fromY = toX = toY = -1;
4853         } else {
4854           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4855           //                 So we parse the long-algebraic move string in stead of the SAN move
4856           int valid; char buf[MSG_SIZ], *prom;
4857
4858           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4859                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4860           // str looks something like "Q/a1-a2"; kill the slash
4861           if(str[1] == '/')
4862             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4863           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4864           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4865                 strcat(buf, prom); // long move lacks promo specification!
4866           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4867                 if(appData.debugMode)
4868                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4869                 safeStrCpy(move_str, buf, MSG_SIZ);
4870           }
4871           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4872                                 &fromX, &fromY, &toX, &toY, &promoChar)
4873                || ParseOneMove(buf, moveNum - 1, &moveType,
4874                                 &fromX, &fromY, &toX, &toY, &promoChar);
4875           // end of long SAN patch
4876           if (valid) {
4877             (void) CoordsToAlgebraic(boards[moveNum - 1],
4878                                      PosFlags(moveNum - 1),
4879                                      fromY, fromX, toY, toX, promoChar,
4880                                      parseList[moveNum-1]);
4881             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4882               case MT_NONE:
4883               case MT_STALEMATE:
4884               default:
4885                 break;
4886               case MT_CHECK:
4887                 if(!IS_SHOGI(gameInfo.variant))
4888                     strcat(parseList[moveNum - 1], "+");
4889                 break;
4890               case MT_CHECKMATE:
4891               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4892                 strcat(parseList[moveNum - 1], "#");
4893                 break;
4894             }
4895             strcat(parseList[moveNum - 1], " ");
4896             strcat(parseList[moveNum - 1], elapsed_time);
4897             /* currentMoveString is set as a side-effect of ParseOneMove */
4898             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4899             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4900             strcat(moveList[moveNum - 1], "\n");
4901
4902             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4903                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4904               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4905                 ChessSquare old, new = boards[moveNum][k][j];
4906                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4907                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4908                   if(old == new) continue;
4909                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4910                   else if(new == WhiteWazir || new == BlackWazir) {
4911                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4912                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4913                       else boards[moveNum][k][j] = old; // preserve type of Gold
4914                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4915                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4916               }
4917           } else {
4918             /* Move from ICS was illegal!?  Punt. */
4919             if (appData.debugMode) {
4920               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4921               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4922             }
4923             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4924             strcat(parseList[moveNum - 1], " ");
4925             strcat(parseList[moveNum - 1], elapsed_time);
4926             moveList[moveNum - 1][0] = NULLCHAR;
4927             fromX = fromY = toX = toY = -1;
4928           }
4929         }
4930   if (appData.debugMode) {
4931     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4932     setbuf(debugFP, NULL);
4933   }
4934
4935 #if ZIPPY
4936         /* Send move to chess program (BEFORE animating it). */
4937         if (appData.zippyPlay && !newGame && newMove &&
4938            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4939
4940             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4941                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4942                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4943                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4944                             move_str);
4945                     DisplayError(str, 0);
4946                 } else {
4947                     if (first.sendTime) {
4948                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4949                     }
4950                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4951                     if (firstMove && !bookHit) {
4952                         firstMove = FALSE;
4953                         if (first.useColors) {
4954                           SendToProgram(gameMode == IcsPlayingWhite ?
4955                                         "white\ngo\n" :
4956                                         "black\ngo\n", &first);
4957                         } else {
4958                           SendToProgram("go\n", &first);
4959                         }
4960                         first.maybeThinking = TRUE;
4961                     }
4962                 }
4963             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4964               if (moveList[moveNum - 1][0] == NULLCHAR) {
4965                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4966                 DisplayError(str, 0);
4967               } else {
4968                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4969                 SendMoveToProgram(moveNum - 1, &first);
4970               }
4971             }
4972         }
4973 #endif
4974     }
4975
4976     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4977         /* If move comes from a remote source, animate it.  If it
4978            isn't remote, it will have already been animated. */
4979         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4980             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4981         }
4982         if (!pausing && appData.highlightLastMove) {
4983             SetHighlights(fromX, fromY, toX, toY);
4984         }
4985     }
4986
4987     /* Start the clocks */
4988     whiteFlag = blackFlag = FALSE;
4989     appData.clockMode = !(basetime == 0 && increment == 0);
4990     if (ticking == 0) {
4991       ics_clock_paused = TRUE;
4992       StopClocks();
4993     } else if (ticking == 1) {
4994       ics_clock_paused = FALSE;
4995     }
4996     if (gameMode == IcsIdle ||
4997         relation == RELATION_OBSERVING_STATIC ||
4998         relation == RELATION_EXAMINING ||
4999         ics_clock_paused)
5000       DisplayBothClocks();
5001     else
5002       StartClocks();
5003
5004     /* Display opponents and material strengths */
5005     if (gameInfo.variant != VariantBughouse &&
5006         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5007         if (tinyLayout || smallLayout) {
5008             if(gameInfo.variant == VariantNormal)
5009               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5010                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5011                     basetime, increment);
5012             else
5013               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5014                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5015                     basetime, increment, (int) gameInfo.variant);
5016         } else {
5017             if(gameInfo.variant == VariantNormal)
5018               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5019                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5020                     basetime, increment);
5021             else
5022               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5023                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024                     basetime, increment, VariantName(gameInfo.variant));
5025         }
5026         DisplayTitle(str);
5027   if (appData.debugMode) {
5028     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5029   }
5030     }
5031
5032
5033     /* Display the board */
5034     if (!pausing && !appData.noGUI) {
5035
5036       if (appData.premove)
5037           if (!gotPremove ||
5038              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5039              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5040               ClearPremoveHighlights();
5041
5042       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5043         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5044       DrawPosition(j, boards[currentMove]);
5045
5046       DisplayMove(moveNum - 1);
5047       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5048             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5049               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5050         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5051       }
5052     }
5053
5054     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5055 #if ZIPPY
5056     if(bookHit) { // [HGM] book: simulate book reply
5057         static char bookMove[MSG_SIZ]; // a bit generous?
5058
5059         programStats.nodes = programStats.depth = programStats.time =
5060         programStats.score = programStats.got_only_move = 0;
5061         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5062
5063         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5064         strcat(bookMove, bookHit);
5065         HandleMachineMove(bookMove, &first);
5066     }
5067 #endif
5068 }
5069
5070 void
5071 GetMoveListEvent ()
5072 {
5073     char buf[MSG_SIZ];
5074     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5075         ics_getting_history = H_REQUESTED;
5076         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5077         SendToICS(buf);
5078     }
5079 }
5080
5081 void
5082 SendToBoth (char *msg)
5083 {   // to make it easy to keep two engines in step in dual analysis
5084     SendToProgram(msg, &first);
5085     if(second.analyzing) SendToProgram(msg, &second);
5086 }
5087
5088 void
5089 AnalysisPeriodicEvent (int force)
5090 {
5091     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5092          && !force) || !appData.periodicUpdates)
5093       return;
5094
5095     /* Send . command to Crafty to collect stats */
5096     SendToBoth(".\n");
5097
5098     /* Don't send another until we get a response (this makes
5099        us stop sending to old Crafty's which don't understand
5100        the "." command (sending illegal cmds resets node count & time,
5101        which looks bad)) */
5102     programStats.ok_to_send = 0;
5103 }
5104
5105 void
5106 ics_update_width (int new_width)
5107 {
5108         ics_printf("set width %d\n", new_width);
5109 }
5110
5111 void
5112 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5113 {
5114     char buf[MSG_SIZ];
5115
5116     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5117         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5118             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5119             SendToProgram(buf, cps);
5120             return;
5121         }
5122         // null move in variant where engine does not understand it (for analysis purposes)
5123         SendBoard(cps, moveNum + 1); // send position after move in stead.
5124         return;
5125     }
5126     if (cps->useUsermove) {
5127       SendToProgram("usermove ", cps);
5128     }
5129     if (cps->useSAN) {
5130       char *space;
5131       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5132         int len = space - parseList[moveNum];
5133         memcpy(buf, parseList[moveNum], len);
5134         buf[len++] = '\n';
5135         buf[len] = NULLCHAR;
5136       } else {
5137         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5138       }
5139       SendToProgram(buf, cps);
5140     } else {
5141       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5142         AlphaRank(moveList[moveNum], 4);
5143         SendToProgram(moveList[moveNum], cps);
5144         AlphaRank(moveList[moveNum], 4); // and back
5145       } else
5146       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5147        * the engine. It would be nice to have a better way to identify castle
5148        * moves here. */
5149       if(appData.fischerCastling && cps->useOOCastle) {
5150         int fromX = moveList[moveNum][0] - AAA;
5151         int fromY = moveList[moveNum][1] - ONE;
5152         int toX = moveList[moveNum][2] - AAA;
5153         int toY = moveList[moveNum][3] - ONE;
5154         if((boards[moveNum][fromY][fromX] == WhiteKing
5155             && boards[moveNum][toY][toX] == WhiteRook)
5156            || (boards[moveNum][fromY][fromX] == BlackKing
5157                && boards[moveNum][toY][toX] == BlackRook)) {
5158           if(toX > fromX) SendToProgram("O-O\n", cps);
5159           else SendToProgram("O-O-O\n", cps);
5160         }
5161         else SendToProgram(moveList[moveNum], cps);
5162       } else
5163       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5164         char *m = moveList[moveNum];
5165         static char c[2];
5166         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5167         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
5168           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5169                                                m[2], m[3] - '0',
5170                                                m[5], m[6] - '0',
5171                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5172         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5173           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5174           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5175                                                m[7], m[8] - '0',
5176                                                m[7], m[8] - '0',
5177                                                m[5], m[6] - '0',
5178                                                m[5], m[6] - '0',
5179                                                m[2], m[3] - '0', c);
5180         } else
5181           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5182                                                m[5], m[6] - '0',
5183                                                m[5], m[6] - '0',
5184                                                m[2], m[3] - '0', c);
5185           SendToProgram(buf, cps);
5186       } else
5187       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5188         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5189           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5190           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5191                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5192         } else
5193           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5194                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5195         SendToProgram(buf, cps);
5196       }
5197       else SendToProgram(moveList[moveNum], cps);
5198       /* End of additions by Tord */
5199     }
5200
5201     /* [HGM] setting up the opening has brought engine in force mode! */
5202     /*       Send 'go' if we are in a mode where machine should play. */
5203     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5204         (gameMode == TwoMachinesPlay   ||
5205 #if ZIPPY
5206          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5207 #endif
5208          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5209         SendToProgram("go\n", cps);
5210   if (appData.debugMode) {
5211     fprintf(debugFP, "(extra)\n");
5212   }
5213     }
5214     setboardSpoiledMachineBlack = 0;
5215 }
5216
5217 void
5218 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5219 {
5220     char user_move[MSG_SIZ];
5221     char suffix[4];
5222
5223     if(gameInfo.variant == VariantSChess && promoChar) {
5224         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5225         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5226     } else suffix[0] = NULLCHAR;
5227
5228     switch (moveType) {
5229       default:
5230         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5231                 (int)moveType, fromX, fromY, toX, toY);
5232         DisplayError(user_move + strlen("say "), 0);
5233         break;
5234       case WhiteKingSideCastle:
5235       case BlackKingSideCastle:
5236       case WhiteQueenSideCastleWild:
5237       case BlackQueenSideCastleWild:
5238       /* PUSH Fabien */
5239       case WhiteHSideCastleFR:
5240       case BlackHSideCastleFR:
5241       /* POP Fabien */
5242         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5243         break;
5244       case WhiteQueenSideCastle:
5245       case BlackQueenSideCastle:
5246       case WhiteKingSideCastleWild:
5247       case BlackKingSideCastleWild:
5248       /* PUSH Fabien */
5249       case WhiteASideCastleFR:
5250       case BlackASideCastleFR:
5251       /* POP Fabien */
5252         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5253         break;
5254       case WhiteNonPromotion:
5255       case BlackNonPromotion:
5256         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5257         break;
5258       case WhitePromotion:
5259       case BlackPromotion:
5260         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5261            gameInfo.variant == VariantMakruk)
5262           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5263                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5264                 PieceToChar(WhiteFerz));
5265         else if(gameInfo.variant == VariantGreat)
5266           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5267                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268                 PieceToChar(WhiteMan));
5269         else
5270           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5272                 promoChar);
5273         break;
5274       case WhiteDrop:
5275       case BlackDrop:
5276       drop:
5277         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5278                  ToUpper(PieceToChar((ChessSquare) fromX)),
5279                  AAA + toX, ONE + toY);
5280         break;
5281       case IllegalMove:  /* could be a variant we don't quite understand */
5282         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5283       case NormalMove:
5284       case WhiteCapturesEnPassant:
5285       case BlackCapturesEnPassant:
5286         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5287                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5288         break;
5289     }
5290     SendToICS(user_move);
5291     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5292         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5293 }
5294
5295 void
5296 UploadGameEvent ()
5297 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5298     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5299     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5300     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5301       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5302       return;
5303     }
5304     if(gameMode != IcsExamining) { // is this ever not the case?
5305         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5306
5307         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5308           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5309         } else { // on FICS we must first go to general examine mode
5310           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5311         }
5312         if(gameInfo.variant != VariantNormal) {
5313             // try figure out wild number, as xboard names are not always valid on ICS
5314             for(i=1; i<=36; i++) {
5315               snprintf(buf, MSG_SIZ, "wild/%d", i);
5316                 if(StringToVariant(buf) == gameInfo.variant) break;
5317             }
5318             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5319             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5320             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5321         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5322         SendToICS(ics_prefix);
5323         SendToICS(buf);
5324         if(startedFromSetupPosition || backwardMostMove != 0) {
5325           fen = PositionToFEN(backwardMostMove, NULL, 1);
5326           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5327             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5328             SendToICS(buf);
5329           } else { // FICS: everything has to set by separate bsetup commands
5330             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5331             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5332             SendToICS(buf);
5333             if(!WhiteOnMove(backwardMostMove)) {
5334                 SendToICS("bsetup tomove black\n");
5335             }
5336             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5337             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5338             SendToICS(buf);
5339             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5340             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5341             SendToICS(buf);
5342             i = boards[backwardMostMove][EP_STATUS];
5343             if(i >= 0) { // set e.p.
5344               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5345                 SendToICS(buf);
5346             }
5347             bsetup++;
5348           }
5349         }
5350       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5351             SendToICS("bsetup done\n"); // switch to normal examining.
5352     }
5353     for(i = backwardMostMove; i<last; i++) {
5354         char buf[20];
5355         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5356         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5357             int len = strlen(moveList[i]);
5358             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5359             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5360         }
5361         SendToICS(buf);
5362     }
5363     SendToICS(ics_prefix);
5364     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5365 }
5366
5367 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5368 int legNr = 1;
5369
5370 void
5371 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5372 {
5373     if (rf == DROP_RANK) {
5374       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5375       sprintf(move, "%c@%c%c\n",
5376                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5377     } else {
5378         if (promoChar == 'x' || promoChar == NULLCHAR) {
5379           sprintf(move, "%c%c%c%c\n",
5380                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5381           if(killX >= 0 && killY >= 0) {
5382             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5383             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5384           }
5385         } else {
5386             sprintf(move, "%c%c%c%c%c\n",
5387                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5388           if(killX >= 0 && killY >= 0) {
5389             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5390             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5391           }
5392         }
5393     }
5394 }
5395
5396 void
5397 ProcessICSInitScript (FILE *f)
5398 {
5399     char buf[MSG_SIZ];
5400
5401     while (fgets(buf, MSG_SIZ, f)) {
5402         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5403     }
5404
5405     fclose(f);
5406 }
5407
5408
5409 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5410 int dragging;
5411 static ClickType lastClickType;
5412
5413 int
5414 PieceInString (char *s, ChessSquare piece)
5415 {
5416   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5417   while((p = strchr(s, ID))) {
5418     if(!suffix || p[1] == suffix) return TRUE;
5419     s = p;
5420   }
5421   return FALSE;
5422 }
5423
5424 int
5425 Partner (ChessSquare *p)
5426 { // change piece into promotion partner if one shogi-promotes to the other
5427   ChessSquare partner = promoPartner[*p];
5428   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5429   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5430   *p = partner;
5431   return 1;
5432 }
5433
5434 void
5435 Sweep (int step)
5436 {
5437     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5438     static int toggleFlag;
5439     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5440     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5441     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5442     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5443     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5444     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5445     do {
5446         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5447         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5448         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5449         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5450         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5451         if(!step) step = -1;
5452     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5453             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5454             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5455             promoSweep == pawn ||
5456             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5457             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5458     if(toX >= 0) {
5459         int victim = boards[currentMove][toY][toX];
5460         boards[currentMove][toY][toX] = promoSweep;
5461         DrawPosition(FALSE, boards[currentMove]);
5462         boards[currentMove][toY][toX] = victim;
5463     } else
5464     ChangeDragPiece(promoSweep);
5465 }
5466
5467 int
5468 PromoScroll (int x, int y)
5469 {
5470   int step = 0;
5471
5472   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5473   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5474   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5475   if(!step) return FALSE;
5476   lastX = x; lastY = y;
5477   if((promoSweep < BlackPawn) == flipView) step = -step;
5478   if(step > 0) selectFlag = 1;
5479   if(!selectFlag) Sweep(step);
5480   return FALSE;
5481 }
5482
5483 void
5484 NextPiece (int step)
5485 {
5486     ChessSquare piece = boards[currentMove][toY][toX];
5487     do {
5488         pieceSweep -= step;
5489         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5490         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5491         if(!step) step = -1;
5492     } while(PieceToChar(pieceSweep) == '.');
5493     boards[currentMove][toY][toX] = pieceSweep;
5494     DrawPosition(FALSE, boards[currentMove]);
5495     boards[currentMove][toY][toX] = piece;
5496 }
5497 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5498 void
5499 AlphaRank (char *move, int n)
5500 {
5501 //    char *p = move, c; int x, y;
5502
5503     if (appData.debugMode) {
5504         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5505     }
5506
5507     if(move[1]=='*' &&
5508        move[2]>='0' && move[2]<='9' &&
5509        move[3]>='a' && move[3]<='x'    ) {
5510         move[1] = '@';
5511         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5512         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5513     } else
5514     if(move[0]>='0' && move[0]<='9' &&
5515        move[1]>='a' && move[1]<='x' &&
5516        move[2]>='0' && move[2]<='9' &&
5517        move[3]>='a' && move[3]<='x'    ) {
5518         /* input move, Shogi -> normal */
5519         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5520         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5521         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5522         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5523     } else
5524     if(move[1]=='@' &&
5525        move[3]>='0' && move[3]<='9' &&
5526        move[2]>='a' && move[2]<='x'    ) {
5527         move[1] = '*';
5528         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5529         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5530     } else
5531     if(
5532        move[0]>='a' && move[0]<='x' &&
5533        move[3]>='0' && move[3]<='9' &&
5534        move[2]>='a' && move[2]<='x'    ) {
5535          /* output move, normal -> Shogi */
5536         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5537         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5538         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5539         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5540         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5541     }
5542     if (appData.debugMode) {
5543         fprintf(debugFP, "   out = '%s'\n", move);
5544     }
5545 }
5546
5547 char yy_textstr[8000];
5548
5549 /* Parser for moves from gnuchess, ICS, or user typein box */
5550 Boolean
5551 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5552 {
5553     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5554
5555     switch (*moveType) {
5556       case WhitePromotion:
5557       case BlackPromotion:
5558       case WhiteNonPromotion:
5559       case BlackNonPromotion:
5560       case NormalMove:
5561       case FirstLeg:
5562       case WhiteCapturesEnPassant:
5563       case BlackCapturesEnPassant:
5564       case WhiteKingSideCastle:
5565       case WhiteQueenSideCastle:
5566       case BlackKingSideCastle:
5567       case BlackQueenSideCastle:
5568       case WhiteKingSideCastleWild:
5569       case WhiteQueenSideCastleWild:
5570       case BlackKingSideCastleWild:
5571       case BlackQueenSideCastleWild:
5572       /* Code added by Tord: */
5573       case WhiteHSideCastleFR:
5574       case WhiteASideCastleFR:
5575       case BlackHSideCastleFR:
5576       case BlackASideCastleFR:
5577       /* End of code added by Tord */
5578       case IllegalMove:         /* bug or odd chess variant */
5579         if(currentMoveString[1] == '@') { // illegal drop
5580           *fromX = WhiteOnMove(moveNum) ?
5581             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5582             (int) CharToPiece(ToLower(currentMoveString[0]));
5583           goto drop;
5584         }
5585         *fromX = currentMoveString[0] - AAA;
5586         *fromY = currentMoveString[1] - ONE;
5587         *toX = currentMoveString[2] - AAA;
5588         *toY = currentMoveString[3] - ONE;
5589         *promoChar = currentMoveString[4];
5590         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5591         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5592             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5593     if (appData.debugMode) {
5594         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5595     }
5596             *fromX = *fromY = *toX = *toY = 0;
5597             return FALSE;
5598         }
5599         if (appData.testLegality) {
5600           return (*moveType != IllegalMove);
5601         } else {
5602           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5603                          // [HGM] lion: if this is a double move we are less critical
5604                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5605         }
5606
5607       case WhiteDrop:
5608       case BlackDrop:
5609         *fromX = *moveType == WhiteDrop ?
5610           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5611           (int) CharToPiece(ToLower(currentMoveString[0]));
5612       drop:
5613         *fromY = DROP_RANK;
5614         *toX = currentMoveString[2] - AAA;
5615         *toY = currentMoveString[3] - ONE;
5616         *promoChar = NULLCHAR;
5617         return TRUE;
5618
5619       case AmbiguousMove:
5620       case ImpossibleMove:
5621       case EndOfFile:
5622       case ElapsedTime:
5623       case Comment:
5624       case PGNTag:
5625       case NAG:
5626       case WhiteWins:
5627       case BlackWins:
5628       case GameIsDrawn:
5629       default:
5630     if (appData.debugMode) {
5631         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5632     }
5633         /* bug? */
5634         *fromX = *fromY = *toX = *toY = 0;
5635         *promoChar = NULLCHAR;
5636         return FALSE;
5637     }
5638 }
5639
5640 Boolean pushed = FALSE;
5641 char *lastParseAttempt;
5642
5643 void
5644 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5645 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5646   int fromX, fromY, toX, toY; char promoChar;
5647   ChessMove moveType;
5648   Boolean valid;
5649   int nr = 0;
5650
5651   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5652   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5653     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5654     pushed = TRUE;
5655   }
5656   endPV = forwardMostMove;
5657   do {
5658     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5659     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5660     lastParseAttempt = pv;
5661     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5662     if(!valid && nr == 0 &&
5663        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5664         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5665         // Hande case where played move is different from leading PV move
5666         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5667         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5668         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5669         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5670           endPV += 2; // if position different, keep this
5671           moveList[endPV-1][0] = fromX + AAA;
5672           moveList[endPV-1][1] = fromY + ONE;
5673           moveList[endPV-1][2] = toX + AAA;
5674           moveList[endPV-1][3] = toY + ONE;
5675           parseList[endPV-1][0] = NULLCHAR;
5676           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5677         }
5678       }
5679     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5680     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5681     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5682     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5683         valid++; // allow comments in PV
5684         continue;
5685     }
5686     nr++;
5687     if(endPV+1 > framePtr) break; // no space, truncate
5688     if(!valid) break;
5689     endPV++;
5690     CopyBoard(boards[endPV], boards[endPV-1]);
5691     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5692     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5693     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5694     CoordsToAlgebraic(boards[endPV - 1],
5695                              PosFlags(endPV - 1),
5696                              fromY, fromX, toY, toX, promoChar,
5697                              parseList[endPV - 1]);
5698   } while(valid);
5699   if(atEnd == 2) return; // used hidden, for PV conversion
5700   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5701   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5702   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5703                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5704   DrawPosition(TRUE, boards[currentMove]);
5705 }
5706
5707 int
5708 MultiPV (ChessProgramState *cps, int kind)
5709 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5710         int i;
5711         for(i=0; i<cps->nrOptions; i++) {
5712             char *s = cps->option[i].name;
5713             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5714             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5715                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5716         }
5717         return -1;
5718 }
5719
5720 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5721 static int multi, pv_margin;
5722 static ChessProgramState *activeCps;
5723
5724 Boolean
5725 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5726 {
5727         int startPV, lineStart, origIndex = index;
5728         char *p, buf2[MSG_SIZ];
5729         ChessProgramState *cps = (pane ? &second : &first);
5730
5731         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5732         lastX = x; lastY = y;
5733         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5734         lineStart = startPV = index;
5735         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5736         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5737         index = startPV;
5738         do{ while(buf[index] && buf[index] != '\n') index++;
5739         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5740         buf[index] = 0;
5741         if(lineStart == 0 && gameMode == AnalyzeMode) {
5742             int n = 0;
5743             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5744             if(n == 0) { // click not on "fewer" or "more"
5745                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5746                     pv_margin = cps->option[multi].value;
5747                     activeCps = cps; // non-null signals margin adjustment
5748                 }
5749             } else if((multi = MultiPV(cps, 1)) >= 0) {
5750                 n += cps->option[multi].value; if(n < 1) n = 1;
5751                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5752                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5753                 cps->option[multi].value = n;
5754                 *start = *end = 0;
5755                 return FALSE;
5756             }
5757         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5758                 ExcludeClick(origIndex - lineStart);
5759                 return FALSE;
5760         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5761                 Collapse(origIndex - lineStart);
5762                 return FALSE;
5763         }
5764         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5765         *start = startPV; *end = index-1;
5766         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5767         return TRUE;
5768 }
5769
5770 char *
5771 PvToSAN (char *pv)
5772 {
5773         static char buf[10*MSG_SIZ];
5774         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5775         *buf = NULLCHAR;
5776         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5777         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5778         for(i = forwardMostMove; i<endPV; i++){
5779             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5780             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5781             k += strlen(buf+k);
5782         }
5783         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5784         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5785         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5786         endPV = savedEnd;
5787         return buf;
5788 }
5789
5790 Boolean
5791 LoadPV (int x, int y)
5792 { // called on right mouse click to load PV
5793   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5794   lastX = x; lastY = y;
5795   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5796   extendGame = FALSE;
5797   return TRUE;
5798 }
5799
5800 void
5801 UnLoadPV ()
5802 {
5803   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5804   if(activeCps) {
5805     if(pv_margin != activeCps->option[multi].value) {
5806       char buf[MSG_SIZ];
5807       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5808       SendToProgram(buf, activeCps);
5809       activeCps->option[multi].value = pv_margin;
5810     }
5811     activeCps = NULL;
5812     return;
5813   }
5814   if(endPV < 0) return;
5815   if(appData.autoCopyPV) CopyFENToClipboard();
5816   endPV = -1;
5817   if(extendGame && currentMove > forwardMostMove) {
5818         Boolean saveAnimate = appData.animate;
5819         if(pushed) {
5820             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5821                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5822             } else storedGames--; // abandon shelved tail of original game
5823         }
5824         pushed = FALSE;
5825         forwardMostMove = currentMove;
5826         currentMove = oldFMM;
5827         appData.animate = FALSE;
5828         ToNrEvent(forwardMostMove);
5829         appData.animate = saveAnimate;
5830   }
5831   currentMove = forwardMostMove;
5832   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5833   ClearPremoveHighlights();
5834   DrawPosition(TRUE, boards[currentMove]);
5835 }
5836
5837 void
5838 MovePV (int x, int y, int h)
5839 { // step through PV based on mouse coordinates (called on mouse move)
5840   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5841
5842   if(activeCps) { // adjusting engine's multi-pv margin
5843     if(x > lastX) pv_margin++; else
5844     if(x < lastX) pv_margin -= (pv_margin > 0);
5845     if(x != lastX) {
5846       char buf[MSG_SIZ];
5847       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5848       DisplayMessage(buf, "");
5849     }
5850     lastX = x;
5851     return;
5852   }
5853   // we must somehow check if right button is still down (might be released off board!)
5854   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5855   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5856   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5857   if(!step) return;
5858   lastX = x; lastY = y;
5859
5860   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5861   if(endPV < 0) return;
5862   if(y < margin) step = 1; else
5863   if(y > h - margin) step = -1;
5864   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5865   currentMove += step;
5866   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5867   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5868                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5869   DrawPosition(FALSE, boards[currentMove]);
5870 }
5871
5872
5873 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5874 // All positions will have equal probability, but the current method will not provide a unique
5875 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5876 #define DARK 1
5877 #define LITE 2
5878 #define ANY 3
5879
5880 int squaresLeft[4];
5881 int piecesLeft[(int)BlackPawn];
5882 int seed, nrOfShuffles;
5883
5884 void
5885 GetPositionNumber ()
5886 {       // sets global variable seed
5887         int i;
5888
5889         seed = appData.defaultFrcPosition;
5890         if(seed < 0) { // randomize based on time for negative FRC position numbers
5891                 for(i=0; i<50; i++) seed += random();
5892                 seed = random() ^ random() >> 8 ^ random() << 8;
5893                 if(seed<0) seed = -seed;
5894         }
5895 }
5896
5897 int
5898 put (Board board, int pieceType, int rank, int n, int shade)
5899 // put the piece on the (n-1)-th empty squares of the given shade
5900 {
5901         int i;
5902
5903         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5904                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5905                         board[rank][i] = (ChessSquare) pieceType;
5906                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5907                         squaresLeft[ANY]--;
5908                         piecesLeft[pieceType]--;
5909                         return i;
5910                 }
5911         }
5912         return -1;
5913 }
5914
5915
5916 void
5917 AddOnePiece (Board board, int pieceType, int rank, int shade)
5918 // calculate where the next piece goes, (any empty square), and put it there
5919 {
5920         int i;
5921
5922         i = seed % squaresLeft[shade];
5923         nrOfShuffles *= squaresLeft[shade];
5924         seed /= squaresLeft[shade];
5925         put(board, pieceType, rank, i, shade);
5926 }
5927
5928 void
5929 AddTwoPieces (Board board, int pieceType, int rank)
5930 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5931 {
5932         int i, n=squaresLeft[ANY], j=n-1, k;
5933
5934         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5935         i = seed % k;  // pick one
5936         nrOfShuffles *= k;
5937         seed /= k;
5938         while(i >= j) i -= j--;
5939         j = n - 1 - j; i += j;
5940         put(board, pieceType, rank, j, ANY);
5941         put(board, pieceType, rank, i, ANY);
5942 }
5943
5944 void
5945 SetUpShuffle (Board board, int number)
5946 {
5947         int i, p, first=1;
5948
5949         GetPositionNumber(); nrOfShuffles = 1;
5950
5951         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5952         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5953         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5954
5955         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5956
5957         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5958             p = (int) board[0][i];
5959             if(p < (int) BlackPawn) piecesLeft[p] ++;
5960             board[0][i] = EmptySquare;
5961         }
5962
5963         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5964             // shuffles restricted to allow normal castling put KRR first
5965             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5966                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5967             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5968                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5969             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5970                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5971             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5972                 put(board, WhiteRook, 0, 0, ANY);
5973             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5974         }
5975
5976         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5977             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5978             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5979                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5980                 while(piecesLeft[p] >= 2) {
5981                     AddOnePiece(board, p, 0, LITE);
5982                     AddOnePiece(board, p, 0, DARK);
5983                 }
5984                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5985             }
5986
5987         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5988             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5989             // but we leave King and Rooks for last, to possibly obey FRC restriction
5990             if(p == (int)WhiteRook) continue;
5991             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5992             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5993         }
5994
5995         // now everything is placed, except perhaps King (Unicorn) and Rooks
5996
5997         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5998             // Last King gets castling rights
5999             while(piecesLeft[(int)WhiteUnicorn]) {
6000                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6001                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6002             }
6003
6004             while(piecesLeft[(int)WhiteKing]) {
6005                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6006                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6007             }
6008
6009
6010         } else {
6011             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6012             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6013         }
6014
6015         // Only Rooks can be left; simply place them all
6016         while(piecesLeft[(int)WhiteRook]) {
6017                 i = put(board, WhiteRook, 0, 0, ANY);
6018                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6019                         if(first) {
6020                                 first=0;
6021                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6022                         }
6023                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6024                 }
6025         }
6026         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6027             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6028         }
6029
6030         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6031 }
6032
6033 int
6034 ptclen (const char *s, char *escapes)
6035 {
6036     int n = 0;
6037     if(!*escapes) return strlen(s);
6038     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6039     return n;
6040 }
6041
6042 int
6043 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6044 /* [HGM] moved here from winboard.c because of its general usefulness */
6045 /*       Basically a safe strcpy that uses the last character as King */
6046 {
6047     int result = FALSE; int NrPieces;
6048     unsigned char partner[EmptySquare];
6049
6050     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6051                     && NrPieces >= 12 && !(NrPieces&1)) {
6052         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6053
6054         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6055         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6056             char *p, c=0;
6057             if(map[j] == '/') offs = WhitePBishop - i, j++;
6058             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6059             table[i+offs] = map[j++];
6060             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6061             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6062             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6063         }
6064         table[(int) WhiteKing]  = map[j++];
6065         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6066             char *p, c=0;
6067             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6068             i = WHITE_TO_BLACK ii;
6069             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6070             table[i+offs] = map[j++];
6071             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6072             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6073             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6074         }
6075         table[(int) BlackKing]  = map[j++];
6076
6077
6078         if(*escapes) { // set up promotion pairing
6079             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6080             // pieceToChar entirely filled, so we can look up specified partners
6081             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6082                 int c = table[i];
6083                 if(c == '^' || c == '-') { // has specified partner
6084                     int p;
6085                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6086                     if(c == '^') table[i] = '+';
6087                     if(p < EmptySquare) {
6088                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6089                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6090                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6091                     }
6092                 } else if(c == '*') {
6093                     table[i] = partner[i];
6094                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6095                 }
6096             }
6097         }
6098
6099         result = TRUE;
6100     }
6101
6102     return result;
6103 }
6104
6105 int
6106 SetCharTable (unsigned char *table, const char * map)
6107 {
6108     return SetCharTableEsc(table, map, "");
6109 }
6110
6111 void
6112 Prelude (Board board)
6113 {       // [HGM] superchess: random selection of exo-pieces
6114         int i, j, k; ChessSquare p;
6115         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6116
6117         GetPositionNumber(); // use FRC position number
6118
6119         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6120             SetCharTable(pieceToChar, appData.pieceToCharTable);
6121             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6122                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6123         }
6124
6125         j = seed%4;                 seed /= 4;
6126         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6127         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6128         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6129         j = seed%3 + (seed%3 >= j); seed /= 3;
6130         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6133         j = seed%3;                 seed /= 3;
6134         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6135         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6136         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6137         j = seed%2 + (seed%2 >= j); seed /= 2;
6138         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6139         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6140         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6141         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6142         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6143         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6144         put(board, exoPieces[0],    0, 0, ANY);
6145         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6146 }
6147
6148 void
6149 InitPosition (int redraw)
6150 {
6151     ChessSquare (* pieces)[BOARD_FILES];
6152     int i, j, pawnRow=1, pieceRows=1, overrule,
6153     oldx = gameInfo.boardWidth,
6154     oldy = gameInfo.boardHeight,
6155     oldh = gameInfo.holdingsWidth;
6156     static int oldv;
6157
6158     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6159
6160     /* [AS] Initialize pv info list [HGM] and game status */
6161     {
6162         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6163             pvInfoList[i].depth = 0;
6164             boards[i][EP_STATUS] = EP_NONE;
6165             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6166         }
6167
6168         initialRulePlies = 0; /* 50-move counter start */
6169
6170         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6171         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6172     }
6173
6174
6175     /* [HGM] logic here is completely changed. In stead of full positions */
6176     /* the initialized data only consist of the two backranks. The switch */
6177     /* selects which one we will use, which is than copied to the Board   */
6178     /* initialPosition, which for the rest is initialized by Pawns and    */
6179     /* empty squares. This initial position is then copied to boards[0],  */
6180     /* possibly after shuffling, so that it remains available.            */
6181
6182     gameInfo.holdingsWidth = 0; /* default board sizes */
6183     gameInfo.boardWidth    = 8;
6184     gameInfo.boardHeight   = 8;
6185     gameInfo.holdingsSize  = 0;
6186     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6187     for(i=0; i<BOARD_FILES-6; i++)
6188       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6189     initialPosition[EP_STATUS] = EP_NONE;
6190     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6191     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6192     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6193          SetCharTable(pieceNickName, appData.pieceNickNames);
6194     else SetCharTable(pieceNickName, "............");
6195     pieces = FIDEArray;
6196
6197     switch (gameInfo.variant) {
6198     case VariantFischeRandom:
6199       shuffleOpenings = TRUE;
6200       appData.fischerCastling = TRUE;
6201     default:
6202       break;
6203     case VariantShatranj:
6204       pieces = ShatranjArray;
6205       nrCastlingRights = 0;
6206       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6207       break;
6208     case VariantMakruk:
6209       pieces = makrukArray;
6210       nrCastlingRights = 0;
6211       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6212       break;
6213     case VariantASEAN:
6214       pieces = aseanArray;
6215       nrCastlingRights = 0;
6216       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6217       break;
6218     case VariantTwoKings:
6219       pieces = twoKingsArray;
6220       break;
6221     case VariantGrand:
6222       pieces = GrandArray;
6223       nrCastlingRights = 0;
6224       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6225       gameInfo.boardWidth = 10;
6226       gameInfo.boardHeight = 10;
6227       gameInfo.holdingsSize = 7;
6228       break;
6229     case VariantCapaRandom:
6230       shuffleOpenings = TRUE;
6231       appData.fischerCastling = TRUE;
6232     case VariantCapablanca:
6233       pieces = CapablancaArray;
6234       gameInfo.boardWidth = 10;
6235       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6236       break;
6237     case VariantGothic:
6238       pieces = GothicArray;
6239       gameInfo.boardWidth = 10;
6240       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6241       break;
6242     case VariantSChess:
6243       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6244       gameInfo.holdingsSize = 7;
6245       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6246       break;
6247     case VariantJanus:
6248       pieces = JanusArray;
6249       gameInfo.boardWidth = 10;
6250       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6251       nrCastlingRights = 6;
6252         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6253         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6254         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6255         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6256         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6257         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6258       break;
6259     case VariantFalcon:
6260       pieces = FalconArray;
6261       gameInfo.boardWidth = 10;
6262       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6263       break;
6264     case VariantXiangqi:
6265       pieces = XiangqiArray;
6266       gameInfo.boardWidth  = 9;
6267       gameInfo.boardHeight = 10;
6268       nrCastlingRights = 0;
6269       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6270       break;
6271     case VariantShogi:
6272       pieces = ShogiArray;
6273       gameInfo.boardWidth  = 9;
6274       gameInfo.boardHeight = 9;
6275       gameInfo.holdingsSize = 7;
6276       nrCastlingRights = 0;
6277       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6278       break;
6279     case VariantChu:
6280       pieces = ChuArray; pieceRows = 3;
6281       gameInfo.boardWidth  = 12;
6282       gameInfo.boardHeight = 12;
6283       nrCastlingRights = 0;
6284 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6285   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6286       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6287                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6288       break;
6289     case VariantCourier:
6290       pieces = CourierArray;
6291       gameInfo.boardWidth  = 12;
6292       nrCastlingRights = 0;
6293       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6294       break;
6295     case VariantKnightmate:
6296       pieces = KnightmateArray;
6297       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6298       break;
6299     case VariantSpartan:
6300       pieces = SpartanArray;
6301       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6302       break;
6303     case VariantLion:
6304       pieces = lionArray;
6305       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6306       break;
6307     case VariantChuChess:
6308       pieces = ChuChessArray;
6309       gameInfo.boardWidth = 10;
6310       gameInfo.boardHeight = 10;
6311       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6312       break;
6313     case VariantFairy:
6314       pieces = fairyArray;
6315       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6316       break;
6317     case VariantGreat:
6318       pieces = GreatArray;
6319       gameInfo.boardWidth = 10;
6320       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6321       gameInfo.holdingsSize = 8;
6322       break;
6323     case VariantSuper:
6324       pieces = FIDEArray;
6325       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6326       gameInfo.holdingsSize = 8;
6327       startedFromSetupPosition = TRUE;
6328       break;
6329     case VariantCrazyhouse:
6330     case VariantBughouse:
6331       pieces = FIDEArray;
6332       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6333       gameInfo.holdingsSize = 5;
6334       break;
6335     case VariantWildCastle:
6336       pieces = FIDEArray;
6337       /* !!?shuffle with kings guaranteed to be on d or e file */
6338       shuffleOpenings = 1;
6339       break;
6340     case VariantNoCastle:
6341       pieces = FIDEArray;
6342       nrCastlingRights = 0;
6343       /* !!?unconstrained back-rank shuffle */
6344       shuffleOpenings = 1;
6345       break;
6346     }
6347
6348     overrule = 0;
6349     if(appData.NrFiles >= 0) {
6350         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6351         gameInfo.boardWidth = appData.NrFiles;
6352     }
6353     if(appData.NrRanks >= 0) {
6354         gameInfo.boardHeight = appData.NrRanks;
6355     }
6356     if(appData.holdingsSize >= 0) {
6357         i = appData.holdingsSize;
6358         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6359         gameInfo.holdingsSize = i;
6360     }
6361     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6362     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6363         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6364
6365     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6366     if(pawnRow < 1) pawnRow = 1;
6367     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6368        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6369     if(gameInfo.variant == VariantChu) pawnRow = 3;
6370
6371     /* User pieceToChar list overrules defaults */
6372     if(appData.pieceToCharTable != NULL)
6373         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6374
6375     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6376
6377         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6378             s = (ChessSquare) 0; /* account holding counts in guard band */
6379         for( i=0; i<BOARD_HEIGHT; i++ )
6380             initialPosition[i][j] = s;
6381
6382         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6383         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6384         initialPosition[pawnRow][j] = WhitePawn;
6385         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6386         if(gameInfo.variant == VariantXiangqi) {
6387             if(j&1) {
6388                 initialPosition[pawnRow][j] =
6389                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6390                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6391                    initialPosition[2][j] = WhiteCannon;
6392                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6393                 }
6394             }
6395         }
6396         if(gameInfo.variant == VariantChu) {
6397              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6398                initialPosition[pawnRow+1][j] = WhiteCobra,
6399                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6400              for(i=1; i<pieceRows; i++) {
6401                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6402                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6403              }
6404         }
6405         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6406             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6407                initialPosition[0][j] = WhiteRook;
6408                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6409             }
6410         }
6411         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6412     }
6413     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6414     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6415
6416             j=BOARD_LEFT+1;
6417             initialPosition[1][j] = WhiteBishop;
6418             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6419             j=BOARD_RGHT-2;
6420             initialPosition[1][j] = WhiteRook;
6421             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6422     }
6423
6424     if( nrCastlingRights == -1) {
6425         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6426         /*       This sets default castling rights from none to normal corners   */
6427         /* Variants with other castling rights must set them themselves above    */
6428         nrCastlingRights = 6;
6429
6430         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6431         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6432         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6433         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6434         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6435         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6436      }
6437
6438      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6439      if(gameInfo.variant == VariantGreat) { // promotion commoners
6440         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6441         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6442         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6443         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6444      }
6445      if( gameInfo.variant == VariantSChess ) {
6446       initialPosition[1][0] = BlackMarshall;
6447       initialPosition[2][0] = BlackAngel;
6448       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6449       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6450       initialPosition[1][1] = initialPosition[2][1] =
6451       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6452      }
6453   if (appData.debugMode) {
6454     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6455   }
6456     if(shuffleOpenings) {
6457         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6458         startedFromSetupPosition = TRUE;
6459     }
6460     if(startedFromPositionFile) {
6461       /* [HGM] loadPos: use PositionFile for every new game */
6462       CopyBoard(initialPosition, filePosition);
6463       for(i=0; i<nrCastlingRights; i++)
6464           initialRights[i] = filePosition[CASTLING][i];
6465       startedFromSetupPosition = TRUE;
6466     }
6467     if(*appData.men) LoadPieceDesc(appData.men);
6468
6469     CopyBoard(boards[0], initialPosition);
6470
6471     if(oldx != gameInfo.boardWidth ||
6472        oldy != gameInfo.boardHeight ||
6473        oldv != gameInfo.variant ||
6474        oldh != gameInfo.holdingsWidth
6475                                          )
6476             InitDrawingSizes(-2 ,0);
6477
6478     oldv = gameInfo.variant;
6479     if (redraw)
6480       DrawPosition(TRUE, boards[currentMove]);
6481 }
6482
6483 void
6484 SendBoard (ChessProgramState *cps, int moveNum)
6485 {
6486     char message[MSG_SIZ];
6487
6488     if (cps->useSetboard) {
6489       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6490       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6491       SendToProgram(message, cps);
6492       free(fen);
6493
6494     } else {
6495       ChessSquare *bp;
6496       int i, j, left=0, right=BOARD_WIDTH;
6497       /* Kludge to set black to move, avoiding the troublesome and now
6498        * deprecated "black" command.
6499        */
6500       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6501         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6502
6503       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6504
6505       SendToProgram("edit\n", cps);
6506       SendToProgram("#\n", cps);
6507       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6508         bp = &boards[moveNum][i][left];
6509         for (j = left; j < right; j++, bp++) {
6510           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6511           if ((int) *bp < (int) BlackPawn) {
6512             if(j == BOARD_RGHT+1)
6513                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6514             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6515             if(message[0] == '+' || message[0] == '~') {
6516               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6517                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6518                         AAA + j, ONE + i - '0');
6519             }
6520             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6521                 message[1] = BOARD_RGHT   - 1 - j + '1';
6522                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6523             }
6524             SendToProgram(message, cps);
6525           }
6526         }
6527       }
6528
6529       SendToProgram("c\n", cps);
6530       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6531         bp = &boards[moveNum][i][left];
6532         for (j = left; j < right; j++, bp++) {
6533           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6534           if (((int) *bp != (int) EmptySquare)
6535               && ((int) *bp >= (int) BlackPawn)) {
6536             if(j == BOARD_LEFT-2)
6537                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6538             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6539                     AAA + j, ONE + i - '0');
6540             if(message[0] == '+' || message[0] == '~') {
6541               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6542                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6543                         AAA + j, ONE + i - '0');
6544             }
6545             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6546                 message[1] = BOARD_RGHT   - 1 - j + '1';
6547                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6548             }
6549             SendToProgram(message, cps);
6550           }
6551         }
6552       }
6553
6554       SendToProgram(".\n", cps);
6555     }
6556     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6557 }
6558
6559 char exclusionHeader[MSG_SIZ];
6560 int exCnt, excludePtr;
6561 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6562 static Exclusion excluTab[200];
6563 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6564
6565 static void
6566 WriteMap (int s)
6567 {
6568     int j;
6569     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6570     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6571 }
6572
6573 static void
6574 ClearMap ()
6575 {
6576     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6577     excludePtr = 24; exCnt = 0;
6578     WriteMap(0);
6579 }
6580
6581 static void
6582 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6583 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6584     char buf[2*MOVE_LEN], *p;
6585     Exclusion *e = excluTab;
6586     int i;
6587     for(i=0; i<exCnt; i++)
6588         if(e[i].ff == fromX && e[i].fr == fromY &&
6589            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6590     if(i == exCnt) { // was not in exclude list; add it
6591         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6592         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6593             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6594             return; // abort
6595         }
6596         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6597         excludePtr++; e[i].mark = excludePtr++;
6598         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6599         exCnt++;
6600     }
6601     exclusionHeader[e[i].mark] = state;
6602 }
6603
6604 static int
6605 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6606 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6607     char buf[MSG_SIZ];
6608     int j, k;
6609     ChessMove moveType;
6610     if((signed char)promoChar == -1) { // kludge to indicate best move
6611         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6612             return 1; // if unparsable, abort
6613     }
6614     // update exclusion map (resolving toggle by consulting existing state)
6615     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6616     j = k%8; k >>= 3;
6617     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6618     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6619          excludeMap[k] |=   1<<j;
6620     else excludeMap[k] &= ~(1<<j);
6621     // update header
6622     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6623     // inform engine
6624     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6625     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6626     SendToBoth(buf);
6627     return (state == '+');
6628 }
6629
6630 static void
6631 ExcludeClick (int index)
6632 {
6633     int i, j;
6634     Exclusion *e = excluTab;
6635     if(index < 25) { // none, best or tail clicked
6636         if(index < 13) { // none: include all
6637             WriteMap(0); // clear map
6638             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6639             SendToBoth("include all\n"); // and inform engine
6640         } else if(index > 18) { // tail
6641             if(exclusionHeader[19] == '-') { // tail was excluded
6642                 SendToBoth("include all\n");
6643                 WriteMap(0); // clear map completely
6644                 // now re-exclude selected moves
6645                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6646                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6647             } else { // tail was included or in mixed state
6648                 SendToBoth("exclude all\n");
6649                 WriteMap(0xFF); // fill map completely
6650                 // now re-include selected moves
6651                 j = 0; // count them
6652                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6653                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6654                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6655             }
6656         } else { // best
6657             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6658         }
6659     } else {
6660         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6661             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6662             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6663             break;
6664         }
6665     }
6666 }
6667
6668 ChessSquare
6669 DefaultPromoChoice (int white)
6670 {
6671     ChessSquare result;
6672     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6673        gameInfo.variant == VariantMakruk)
6674         result = WhiteFerz; // no choice
6675     else if(gameInfo.variant == VariantASEAN)
6676         result = WhiteRook; // no choice
6677     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6678         result= WhiteKing; // in Suicide Q is the last thing we want
6679     else if(gameInfo.variant == VariantSpartan)
6680         result = white ? WhiteQueen : WhiteAngel;
6681     else result = WhiteQueen;
6682     if(!white) result = WHITE_TO_BLACK result;
6683     return result;
6684 }
6685
6686 static int autoQueen; // [HGM] oneclick
6687
6688 int
6689 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6690 {
6691     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6692     /* [HGM] add Shogi promotions */
6693     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6694     ChessSquare piece, partner;
6695     ChessMove moveType;
6696     Boolean premove;
6697
6698     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6699     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6700
6701     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6702       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6703         return FALSE;
6704
6705     if(legal[toY][toX] == 4) return FALSE;
6706
6707     piece = boards[currentMove][fromY][fromX];
6708     if(gameInfo.variant == VariantChu) {
6709         promotionZoneSize = BOARD_HEIGHT/3;
6710         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6711         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6712     } else if(gameInfo.variant == VariantShogi) {
6713         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6714         highestPromotingPiece = (int)WhiteAlfil;
6715     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6716         promotionZoneSize = 3;
6717     }
6718
6719     // Treat Lance as Pawn when it is not representing Amazon or Lance
6720     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6721         if(piece == WhiteLance) piece = WhitePawn; else
6722         if(piece == BlackLance) piece = BlackPawn;
6723     }
6724
6725     // next weed out all moves that do not touch the promotion zone at all
6726     if((int)piece >= BlackPawn) {
6727         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6728              return FALSE;
6729         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6730         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6731     } else {
6732         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6733            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6734         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6735              return FALSE;
6736     }
6737
6738     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6739
6740     // weed out mandatory Shogi promotions
6741     if(gameInfo.variant == VariantShogi) {
6742         if(piece >= BlackPawn) {
6743             if(toY == 0 && piece == BlackPawn ||
6744                toY == 0 && piece == BlackQueen ||
6745                toY <= 1 && piece == BlackKnight) {
6746                 *promoChoice = '+';
6747                 return FALSE;
6748             }
6749         } else {
6750             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6751                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6752                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6753                 *promoChoice = '+';
6754                 return FALSE;
6755             }
6756         }
6757     }
6758
6759     // weed out obviously illegal Pawn moves
6760     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6761         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6762         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6763         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6764         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6765         // note we are not allowed to test for valid (non-)capture, due to premove
6766     }
6767
6768     // we either have a choice what to promote to, or (in Shogi) whether to promote
6769     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6770        gameInfo.variant == VariantMakruk) {
6771         ChessSquare p=BlackFerz;  // no choice
6772         while(p < EmptySquare) {  //but make sure we use piece that exists
6773             *promoChoice = PieceToChar(p++);
6774             if(*promoChoice != '.') break;
6775         }
6776         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6777     }
6778     // no sense asking what we must promote to if it is going to explode...
6779     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6780         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6781         return FALSE;
6782     }
6783     // give caller the default choice even if we will not make it
6784     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6785     partner = piece; // pieces can promote if the pieceToCharTable says so
6786     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6787     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6788     if(        sweepSelect && gameInfo.variant != VariantGreat
6789                            && gameInfo.variant != VariantGrand
6790                            && gameInfo.variant != VariantSuper) return FALSE;
6791     if(autoQueen) return FALSE; // predetermined
6792
6793     // suppress promotion popup on illegal moves that are not premoves
6794     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6795               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6796     if(appData.testLegality && !premove) {
6797         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6798                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6799         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6800         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6801             return FALSE;
6802     }
6803
6804     return TRUE;
6805 }
6806
6807 int
6808 InPalace (int row, int column)
6809 {   /* [HGM] for Xiangqi */
6810     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6811          column < (BOARD_WIDTH + 4)/2 &&
6812          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6813     return FALSE;
6814 }
6815
6816 int
6817 PieceForSquare (int x, int y)
6818 {
6819   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6820      return -1;
6821   else
6822      return boards[currentMove][y][x];
6823 }
6824
6825 int
6826 OKToStartUserMove (int x, int y)
6827 {
6828     ChessSquare from_piece;
6829     int white_piece;
6830
6831     if (matchMode) return FALSE;
6832     if (gameMode == EditPosition) return TRUE;
6833
6834     if (x >= 0 && y >= 0)
6835       from_piece = boards[currentMove][y][x];
6836     else
6837       from_piece = EmptySquare;
6838
6839     if (from_piece == EmptySquare) return FALSE;
6840
6841     white_piece = (int)from_piece >= (int)WhitePawn &&
6842       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6843
6844     switch (gameMode) {
6845       case AnalyzeFile:
6846       case TwoMachinesPlay:
6847       case EndOfGame:
6848         return FALSE;
6849
6850       case IcsObserving:
6851       case IcsIdle:
6852         return FALSE;
6853
6854       case MachinePlaysWhite:
6855       case IcsPlayingBlack:
6856         if (appData.zippyPlay) return FALSE;
6857         if (white_piece) {
6858             DisplayMoveError(_("You are playing Black"));
6859             return FALSE;
6860         }
6861         break;
6862
6863       case MachinePlaysBlack:
6864       case IcsPlayingWhite:
6865         if (appData.zippyPlay) return FALSE;
6866         if (!white_piece) {
6867             DisplayMoveError(_("You are playing White"));
6868             return FALSE;
6869         }
6870         break;
6871
6872       case PlayFromGameFile:
6873             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6874       case EditGame:
6875       case AnalyzeMode:
6876         if (!white_piece && WhiteOnMove(currentMove)) {
6877             DisplayMoveError(_("It is White's turn"));
6878             return FALSE;
6879         }
6880         if (white_piece && !WhiteOnMove(currentMove)) {
6881             DisplayMoveError(_("It is Black's turn"));
6882             return FALSE;
6883         }
6884         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6885             /* Editing correspondence game history */
6886             /* Could disallow this or prompt for confirmation */
6887             cmailOldMove = -1;
6888         }
6889         break;
6890
6891       case BeginningOfGame:
6892         if (appData.icsActive) return FALSE;
6893         if (!appData.noChessProgram) {
6894             if (!white_piece) {
6895                 DisplayMoveError(_("You are playing White"));
6896                 return FALSE;
6897             }
6898         }
6899         break;
6900
6901       case Training:
6902         if (!white_piece && WhiteOnMove(currentMove)) {
6903             DisplayMoveError(_("It is White's turn"));
6904             return FALSE;
6905         }
6906         if (white_piece && !WhiteOnMove(currentMove)) {
6907             DisplayMoveError(_("It is Black's turn"));
6908             return FALSE;
6909         }
6910         break;
6911
6912       default:
6913       case IcsExamining:
6914         break;
6915     }
6916     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6917         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6918         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6919         && gameMode != AnalyzeFile && gameMode != Training) {
6920         DisplayMoveError(_("Displayed position is not current"));
6921         return FALSE;
6922     }
6923     return TRUE;
6924 }
6925
6926 Boolean
6927 OnlyMove (int *x, int *y, Boolean captures)
6928 {
6929     DisambiguateClosure cl;
6930     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6931     switch(gameMode) {
6932       case MachinePlaysBlack:
6933       case IcsPlayingWhite:
6934       case BeginningOfGame:
6935         if(!WhiteOnMove(currentMove)) return FALSE;
6936         break;
6937       case MachinePlaysWhite:
6938       case IcsPlayingBlack:
6939         if(WhiteOnMove(currentMove)) return FALSE;
6940         break;
6941       case EditGame:
6942         break;
6943       default:
6944         return FALSE;
6945     }
6946     cl.pieceIn = EmptySquare;
6947     cl.rfIn = *y;
6948     cl.ffIn = *x;
6949     cl.rtIn = -1;
6950     cl.ftIn = -1;
6951     cl.promoCharIn = NULLCHAR;
6952     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6953     if( cl.kind == NormalMove ||
6954         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6955         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6956         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6957       fromX = cl.ff;
6958       fromY = cl.rf;
6959       *x = cl.ft;
6960       *y = cl.rt;
6961       return TRUE;
6962     }
6963     if(cl.kind != ImpossibleMove) return FALSE;
6964     cl.pieceIn = EmptySquare;
6965     cl.rfIn = -1;
6966     cl.ffIn = -1;
6967     cl.rtIn = *y;
6968     cl.ftIn = *x;
6969     cl.promoCharIn = NULLCHAR;
6970     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6971     if( cl.kind == NormalMove ||
6972         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6973         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6974         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6975       fromX = cl.ff;
6976       fromY = cl.rf;
6977       *x = cl.ft;
6978       *y = cl.rt;
6979       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6980       return TRUE;
6981     }
6982     return FALSE;
6983 }
6984
6985 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6986 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6987 int lastLoadGameUseList = FALSE;
6988 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6989 ChessMove lastLoadGameStart = EndOfFile;
6990 int doubleClick;
6991 Boolean addToBookFlag;
6992 static Board rightsBoard, nullBoard;
6993
6994 void
6995 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6996 {
6997     ChessMove moveType;
6998     ChessSquare pup;
6999     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7000
7001     /* Check if the user is playing in turn.  This is complicated because we
7002        let the user "pick up" a piece before it is his turn.  So the piece he
7003        tried to pick up may have been captured by the time he puts it down!
7004        Therefore we use the color the user is supposed to be playing in this
7005        test, not the color of the piece that is currently on the starting
7006        square---except in EditGame mode, where the user is playing both
7007        sides; fortunately there the capture race can't happen.  (It can
7008        now happen in IcsExamining mode, but that's just too bad.  The user
7009        will get a somewhat confusing message in that case.)
7010        */
7011
7012     switch (gameMode) {
7013       case AnalyzeFile:
7014       case TwoMachinesPlay:
7015       case EndOfGame:
7016       case IcsObserving:
7017       case IcsIdle:
7018         /* We switched into a game mode where moves are not accepted,
7019            perhaps while the mouse button was down. */
7020         return;
7021
7022       case MachinePlaysWhite:
7023         /* User is moving for Black */
7024         if (WhiteOnMove(currentMove)) {
7025             DisplayMoveError(_("It is White's turn"));
7026             return;
7027         }
7028         break;
7029
7030       case MachinePlaysBlack:
7031         /* User is moving for White */
7032         if (!WhiteOnMove(currentMove)) {
7033             DisplayMoveError(_("It is Black's turn"));
7034             return;
7035         }
7036         break;
7037
7038       case PlayFromGameFile:
7039             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7040       case EditGame:
7041       case IcsExamining:
7042       case BeginningOfGame:
7043       case AnalyzeMode:
7044       case Training:
7045         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7046         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7047             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7048             /* User is moving for Black */
7049             if (WhiteOnMove(currentMove)) {
7050                 DisplayMoveError(_("It is White's turn"));
7051                 return;
7052             }
7053         } else {
7054             /* User is moving for White */
7055             if (!WhiteOnMove(currentMove)) {
7056                 DisplayMoveError(_("It is Black's turn"));
7057                 return;
7058             }
7059         }
7060         break;
7061
7062       case IcsPlayingBlack:
7063         /* User is moving for Black */
7064         if (WhiteOnMove(currentMove)) {
7065             if (!appData.premove) {
7066                 DisplayMoveError(_("It is White's turn"));
7067             } else if (toX >= 0 && toY >= 0) {
7068                 premoveToX = toX;
7069                 premoveToY = toY;
7070                 premoveFromX = fromX;
7071                 premoveFromY = fromY;
7072                 premovePromoChar = promoChar;
7073                 gotPremove = 1;
7074                 if (appData.debugMode)
7075                     fprintf(debugFP, "Got premove: fromX %d,"
7076                             "fromY %d, toX %d, toY %d\n",
7077                             fromX, fromY, toX, toY);
7078             }
7079             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7080             return;
7081         }
7082         break;
7083
7084       case IcsPlayingWhite:
7085         /* User is moving for White */
7086         if (!WhiteOnMove(currentMove)) {
7087             if (!appData.premove) {
7088                 DisplayMoveError(_("It is Black's turn"));
7089             } else if (toX >= 0 && toY >= 0) {
7090                 premoveToX = toX;
7091                 premoveToY = toY;
7092                 premoveFromX = fromX;
7093                 premoveFromY = fromY;
7094                 premovePromoChar = promoChar;
7095                 gotPremove = 1;
7096                 if (appData.debugMode)
7097                     fprintf(debugFP, "Got premove: fromX %d,"
7098                             "fromY %d, toX %d, toY %d\n",
7099                             fromX, fromY, toX, toY);
7100             }
7101             DrawPosition(TRUE, boards[currentMove]);
7102             return;
7103         }
7104         break;
7105
7106       default:
7107         break;
7108
7109       case EditPosition:
7110         /* EditPosition, empty square, or different color piece;
7111            click-click move is possible */
7112         if (toX == -2 || toY == -2) {
7113             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7114             DrawPosition(FALSE, boards[currentMove]);
7115             return;
7116         } else if (toX >= 0 && toY >= 0) {
7117             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7118                 ChessSquare p = boards[0][rf][ff];
7119                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7120                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7121                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7122                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7123                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7124                     gatingPiece = p;
7125                 }
7126             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7127             boards[0][toY][toX] = boards[0][fromY][fromX];
7128             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7129                 if(boards[0][fromY][0] != EmptySquare) {
7130                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7131                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7132                 }
7133             } else
7134             if(fromX == BOARD_RGHT+1) {
7135                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7136                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7137                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7138                 }
7139             } else
7140             boards[0][fromY][fromX] = gatingPiece;
7141             ClearHighlights();
7142             DrawPosition(FALSE, boards[currentMove]);
7143             return;
7144         }
7145         return;
7146     }
7147
7148     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7149     pup = boards[currentMove][toY][toX];
7150
7151     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7152     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7153          if( pup != EmptySquare ) return;
7154          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7155            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7156                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7157            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7158            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7159            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7160            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7161          fromY = DROP_RANK;
7162     }
7163
7164     /* [HGM] always test for legality, to get promotion info */
7165     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7166                                          fromY, fromX, toY, toX, promoChar);
7167
7168     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7169
7170     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7171
7172     /* [HGM] but possibly ignore an IllegalMove result */
7173     if (appData.testLegality) {
7174         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7175             DisplayMoveError(_("Illegal move"));
7176             return;
7177         }
7178     }
7179
7180     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7181         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7182              ClearPremoveHighlights(); // was included
7183         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7184         DrawPosition(FALSE, NULL);
7185         return;
7186     }
7187
7188     if(addToBookFlag) { // adding moves to book
7189         char buf[MSG_SIZ], move[MSG_SIZ];
7190         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7191         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7192                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7193         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7194         AddBookMove(buf);
7195         addToBookFlag = FALSE;
7196         ClearHighlights();
7197         return;
7198     }
7199
7200     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7201 }
7202
7203 /* Common tail of UserMoveEvent and DropMenuEvent */
7204 int
7205 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7206 {
7207     char *bookHit = 0;
7208
7209     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7210         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7211         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7212         if(WhiteOnMove(currentMove)) {
7213             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7214         } else {
7215             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7216         }
7217     }
7218
7219     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7220        move type in caller when we know the move is a legal promotion */
7221     if(moveType == NormalMove && promoChar)
7222         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7223
7224     /* [HGM] <popupFix> The following if has been moved here from
7225        UserMoveEvent(). Because it seemed to belong here (why not allow
7226        piece drops in training games?), and because it can only be
7227        performed after it is known to what we promote. */
7228     if (gameMode == Training) {
7229       /* compare the move played on the board to the next move in the
7230        * game. If they match, display the move and the opponent's response.
7231        * If they don't match, display an error message.
7232        */
7233       int saveAnimate;
7234       Board testBoard;
7235       CopyBoard(testBoard, boards[currentMove]);
7236       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7237
7238       if (CompareBoards(testBoard, boards[currentMove+1])) {
7239         ForwardInner(currentMove+1);
7240
7241         /* Autoplay the opponent's response.
7242          * if appData.animate was TRUE when Training mode was entered,
7243          * the response will be animated.
7244          */
7245         saveAnimate = appData.animate;
7246         appData.animate = animateTraining;
7247         ForwardInner(currentMove+1);
7248         appData.animate = saveAnimate;
7249
7250         /* check for the end of the game */
7251         if (currentMove >= forwardMostMove) {
7252           gameMode = PlayFromGameFile;
7253           ModeHighlight();
7254           SetTrainingModeOff();
7255           DisplayInformation(_("End of game"));
7256         }
7257       } else {
7258         DisplayError(_("Incorrect move"), 0);
7259       }
7260       return 1;
7261     }
7262
7263   /* Ok, now we know that the move is good, so we can kill
7264      the previous line in Analysis Mode */
7265   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7266                                 && currentMove < forwardMostMove) {
7267     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7268     else forwardMostMove = currentMove;
7269   }
7270
7271   ClearMap();
7272
7273   /* If we need the chess program but it's dead, restart it */
7274   ResurrectChessProgram();
7275
7276   /* A user move restarts a paused game*/
7277   if (pausing)
7278     PauseEvent();
7279
7280   thinkOutput[0] = NULLCHAR;
7281
7282   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7283
7284   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7285     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7286     return 1;
7287   }
7288
7289   if (gameMode == BeginningOfGame) {
7290     if (appData.noChessProgram) {
7291       gameMode = EditGame;
7292       SetGameInfo();
7293     } else {
7294       char buf[MSG_SIZ];
7295       gameMode = MachinePlaysBlack;
7296       StartClocks();
7297       SetGameInfo();
7298       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7299       DisplayTitle(buf);
7300       if (first.sendName) {
7301         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7302         SendToProgram(buf, &first);
7303       }
7304       StartClocks();
7305     }
7306     ModeHighlight();
7307   }
7308
7309   /* Relay move to ICS or chess engine */
7310   if (appData.icsActive) {
7311     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7312         gameMode == IcsExamining) {
7313       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7314         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7315         SendToICS("draw ");
7316         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7317       }
7318       // also send plain move, in case ICS does not understand atomic claims
7319       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7320       ics_user_moved = 1;
7321     }
7322   } else {
7323     if (first.sendTime && (gameMode == BeginningOfGame ||
7324                            gameMode == MachinePlaysWhite ||
7325                            gameMode == MachinePlaysBlack)) {
7326       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7327     }
7328     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7329          // [HGM] book: if program might be playing, let it use book
7330         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7331         first.maybeThinking = TRUE;
7332     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7333         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7334         SendBoard(&first, currentMove+1);
7335         if(second.analyzing) {
7336             if(!second.useSetboard) SendToProgram("undo\n", &second);
7337             SendBoard(&second, currentMove+1);
7338         }
7339     } else {
7340         SendMoveToProgram(forwardMostMove-1, &first);
7341         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7342     }
7343     if (currentMove == cmailOldMove + 1) {
7344       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7345     }
7346   }
7347
7348   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7349
7350   switch (gameMode) {
7351   case EditGame:
7352     if(appData.testLegality)
7353     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7354     case MT_NONE:
7355     case MT_CHECK:
7356       break;
7357     case MT_CHECKMATE:
7358     case MT_STAINMATE:
7359       if (WhiteOnMove(currentMove)) {
7360         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7361       } else {
7362         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7363       }
7364       break;
7365     case MT_STALEMATE:
7366       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7367       break;
7368     }
7369     break;
7370
7371   case MachinePlaysBlack:
7372   case MachinePlaysWhite:
7373     /* disable certain menu options while machine is thinking */
7374     SetMachineThinkingEnables();
7375     break;
7376
7377   default:
7378     break;
7379   }
7380
7381   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7382   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7383
7384   if(bookHit) { // [HGM] book: simulate book reply
7385         static char bookMove[MSG_SIZ]; // a bit generous?
7386
7387         programStats.nodes = programStats.depth = programStats.time =
7388         programStats.score = programStats.got_only_move = 0;
7389         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7390
7391         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7392         strcat(bookMove, bookHit);
7393         HandleMachineMove(bookMove, &first);
7394   }
7395   return 1;
7396 }
7397
7398 void
7399 MarkByFEN(char *fen)
7400 {
7401         int r, f;
7402         if(!appData.markers || !appData.highlightDragging) return;
7403         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7404         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7405         while(*fen) {
7406             int s = 0;
7407             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7408             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7409             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7410             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7411             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7412             if(*fen == 'T') marker[r][f++] = 0; else
7413             if(*fen == 'Y') marker[r][f++] = 1; else
7414             if(*fen == 'G') marker[r][f++] = 3; else
7415             if(*fen == 'B') marker[r][f++] = 4; else
7416             if(*fen == 'C') marker[r][f++] = 5; else
7417             if(*fen == 'M') marker[r][f++] = 6; else
7418             if(*fen == 'W') marker[r][f++] = 7; else
7419             if(*fen == 'D') marker[r][f++] = 8; else
7420             if(*fen == 'R') marker[r][f++] = 2; else {
7421                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7422               f += s; fen -= s>0;
7423             }
7424             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7425             if(r < 0) break;
7426             fen++;
7427         }
7428         DrawPosition(TRUE, NULL);
7429 }
7430
7431 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7432
7433 void
7434 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7435 {
7436     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7437     Markers *m = (Markers *) closure;
7438     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7439                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7440         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7441                          || kind == WhiteCapturesEnPassant
7442                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7443     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7444 }
7445
7446 static int hoverSavedValid;
7447
7448 void
7449 MarkTargetSquares (int clear)
7450 {
7451   int x, y, sum=0;
7452   if(clear) { // no reason to ever suppress clearing
7453     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7454     hoverSavedValid = 0;
7455     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7456   } else {
7457     int capt = 0;
7458     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7459        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7460     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7461     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7462       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7463       if(capt)
7464       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7465     }
7466   }
7467   DrawPosition(FALSE, NULL);
7468 }
7469
7470 int
7471 Explode (Board board, int fromX, int fromY, int toX, int toY)
7472 {
7473     if(gameInfo.variant == VariantAtomic &&
7474        (board[toY][toX] != EmptySquare ||                     // capture?
7475         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7476                          board[fromY][fromX] == BlackPawn   )
7477       )) {
7478         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7479         return TRUE;
7480     }
7481     return FALSE;
7482 }
7483
7484 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7485
7486 int
7487 CanPromote (ChessSquare piece, int y)
7488 {
7489         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7490         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7491         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7492         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7493            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7494           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7495            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7496         return (piece == BlackPawn && y <= zone ||
7497                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7498                 piece == BlackLance && y <= zone ||
7499                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7500 }
7501
7502 void
7503 HoverEvent (int xPix, int yPix, int x, int y)
7504 {
7505         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7506         int r, f;
7507         if(!first.highlight) return;
7508         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7509         if(x == oldX && y == oldY) return; // only do something if we enter new square
7510         oldFromX = fromX; oldFromY = fromY;
7511         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7512           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7513             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7514           hoverSavedValid = 1;
7515         } else if(oldX != x || oldY != y) {
7516           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7517           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7518           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7519             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7520           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7521             char buf[MSG_SIZ];
7522             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7523             SendToProgram(buf, &first);
7524           }
7525           oldX = x; oldY = y;
7526 //        SetHighlights(fromX, fromY, x, y);
7527         }
7528 }
7529
7530 void ReportClick(char *action, int x, int y)
7531 {
7532         char buf[MSG_SIZ]; // Inform engine of what user does
7533         int r, f;
7534         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7535           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7536             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7537         if(!first.highlight || gameMode == EditPosition) return;
7538         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7539         SendToProgram(buf, &first);
7540 }
7541
7542 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7543 Boolean deferChoice;
7544
7545 void
7546 LeftClick (ClickType clickType, int xPix, int yPix)
7547 {
7548     int x, y;
7549     static Boolean saveAnimate;
7550     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7551     char promoChoice = NULLCHAR;
7552     ChessSquare piece;
7553     static TimeMark lastClickTime, prevClickTime;
7554
7555     if(flashing) return;
7556
7557   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7558     x = EventToSquare(xPix, BOARD_WIDTH);
7559     y = EventToSquare(yPix, BOARD_HEIGHT);
7560     if (!flipView && y >= 0) {
7561         y = BOARD_HEIGHT - 1 - y;
7562     }
7563     if (flipView && x >= 0) {
7564         x = BOARD_WIDTH - 1 - x;
7565     }
7566
7567     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7568         static int dummy;
7569         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7570         right = TRUE;
7571         return;
7572     }
7573
7574     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7575
7576     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7577
7578     if (clickType == Press) ErrorPopDown();
7579     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7580
7581     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7582         defaultPromoChoice = promoSweep;
7583         promoSweep = EmptySquare;   // terminate sweep
7584         promoDefaultAltered = TRUE;
7585         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7586     }
7587
7588     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7589         if(clickType == Release) return; // ignore upclick of click-click destination
7590         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7591         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7592         if(gameInfo.holdingsWidth &&
7593                 (WhiteOnMove(currentMove)
7594                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7595                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7596             // click in right holdings, for determining promotion piece
7597             ChessSquare p = boards[currentMove][y][x];
7598             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7599             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7600             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7601                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7602                 fromX = fromY = -1;
7603                 return;
7604             }
7605         }
7606         DrawPosition(FALSE, boards[currentMove]);
7607         return;
7608     }
7609
7610     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7611     if(clickType == Press
7612             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7613               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7614               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7615         return;
7616
7617     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7618         // could be static click on premove from-square: abort premove
7619         gotPremove = 0;
7620         ClearPremoveHighlights();
7621     }
7622
7623     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7624         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7625
7626     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7627         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7628                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7629         defaultPromoChoice = DefaultPromoChoice(side);
7630     }
7631
7632     autoQueen = appData.alwaysPromoteToQueen;
7633
7634     if (fromX == -1) {
7635       int originalY = y;
7636       gatingPiece = EmptySquare;
7637       if (clickType != Press) {
7638         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7639             DragPieceEnd(xPix, yPix); dragging = 0;
7640             DrawPosition(FALSE, NULL);
7641         }
7642         return;
7643       }
7644       doubleClick = FALSE;
7645       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7646         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7647       }
7648       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7649       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7650          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7651          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7652             /* First square */
7653             if (OKToStartUserMove(fromX, fromY)) {
7654                 second = 0;
7655                 ReportClick("lift", x, y);
7656                 MarkTargetSquares(0);
7657                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7658                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7659                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7660                     promoSweep = defaultPromoChoice;
7661                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7662                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7663                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7664                 }
7665                 if (appData.highlightDragging) {
7666                     SetHighlights(fromX, fromY, -1, -1);
7667                 } else {
7668                     ClearHighlights();
7669                 }
7670             } else fromX = fromY = -1;
7671             return;
7672         }
7673     }
7674
7675     /* fromX != -1 */
7676     if (clickType == Press && gameMode != EditPosition) {
7677         ChessSquare fromP;
7678         ChessSquare toP;
7679         int frc;
7680
7681         // ignore off-board to clicks
7682         if(y < 0 || x < 0) return;
7683
7684         /* Check if clicking again on the same color piece */
7685         fromP = boards[currentMove][fromY][fromX];
7686         toP = boards[currentMove][y][x];
7687         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7688         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7689             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7690            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7691              WhitePawn <= toP && toP <= WhiteKing &&
7692              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7693              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7694             (BlackPawn <= fromP && fromP <= BlackKing &&
7695              BlackPawn <= toP && toP <= BlackKing &&
7696              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7697              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7698             /* Clicked again on same color piece -- changed his mind */
7699             second = (x == fromX && y == fromY);
7700             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7701             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7702                 second = FALSE; // first double-click rather than scond click
7703                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7704             }
7705             promoDefaultAltered = FALSE;
7706            if(!second) MarkTargetSquares(1);
7707            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7708             if (appData.highlightDragging) {
7709                 SetHighlights(x, y, -1, -1);
7710             } else {
7711                 ClearHighlights();
7712             }
7713             if (OKToStartUserMove(x, y)) {
7714                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7715                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7716                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7717                  gatingPiece = boards[currentMove][fromY][fromX];
7718                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7719                 fromX = x;
7720                 fromY = y; dragging = 1;
7721                 if(!second) ReportClick("lift", x, y);
7722                 MarkTargetSquares(0);
7723                 DragPieceBegin(xPix, yPix, FALSE);
7724                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7725                     promoSweep = defaultPromoChoice;
7726                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7727                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7728                 }
7729             }
7730            }
7731            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7732            second = FALSE;
7733         }
7734         // ignore clicks on holdings
7735         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7736     }
7737
7738     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7739         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7740         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7741         return;
7742     }
7743
7744     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7745         DragPieceEnd(xPix, yPix); dragging = 0;
7746         if(clearFlag) {
7747             // a deferred attempt to click-click move an empty square on top of a piece
7748             boards[currentMove][y][x] = EmptySquare;
7749             ClearHighlights();
7750             DrawPosition(FALSE, boards[currentMove]);
7751             fromX = fromY = -1; clearFlag = 0;
7752             return;
7753         }
7754         if (appData.animateDragging) {
7755             /* Undo animation damage if any */
7756             DrawPosition(FALSE, NULL);
7757         }
7758         if (second) {
7759             /* Second up/down in same square; just abort move */
7760             second = 0;
7761             fromX = fromY = -1;
7762             gatingPiece = EmptySquare;
7763             ClearHighlights();
7764             gotPremove = 0;
7765             ClearPremoveHighlights();
7766             MarkTargetSquares(-1);
7767             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7768         } else {
7769             /* First upclick in same square; start click-click mode */
7770             SetHighlights(x, y, -1, -1);
7771         }
7772         return;
7773     }
7774
7775     clearFlag = 0;
7776
7777     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7778        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7779         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7780         DisplayMessage(_("only marked squares are legal"),"");
7781         DrawPosition(TRUE, NULL);
7782         return; // ignore to-click
7783     }
7784
7785     /* we now have a different from- and (possibly off-board) to-square */
7786     /* Completed move */
7787     if(!sweepSelecting) {
7788         toX = x;
7789         toY = y;
7790     }
7791
7792     piece = boards[currentMove][fromY][fromX];
7793
7794     saveAnimate = appData.animate;
7795     if (clickType == Press) {
7796         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7797         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7798             // must be Edit Position mode with empty-square selected
7799             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7800             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7801             return;
7802         }
7803         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7804             return;
7805         }
7806         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7807             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7808         } else
7809         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7810         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7811           if(appData.sweepSelect) {
7812             promoSweep = defaultPromoChoice;
7813             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7814             selectFlag = 0; lastX = xPix; lastY = yPix;
7815             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7816             saveFlash = appData.flashCount; appData.flashCount = 0;
7817             Sweep(0); // Pawn that is going to promote: preview promotion piece
7818             sweepSelecting = 1;
7819             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7820             MarkTargetSquares(1);
7821           }
7822           return; // promo popup appears on up-click
7823         }
7824         /* Finish clickclick move */
7825         if (appData.animate || appData.highlightLastMove) {
7826             SetHighlights(fromX, fromY, toX, toY);
7827         } else {
7828             ClearHighlights();
7829         }
7830         MarkTargetSquares(1);
7831     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7832         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7833         *promoRestrict = 0; appData.flashCount = saveFlash;
7834         if (appData.animate || appData.highlightLastMove) {
7835             SetHighlights(fromX, fromY, toX, toY);
7836         } else {
7837             ClearHighlights();
7838         }
7839         MarkTargetSquares(1);
7840     } else {
7841 #if 0
7842 // [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
7843         /* Finish drag move */
7844         if (appData.highlightLastMove) {
7845             SetHighlights(fromX, fromY, toX, toY);
7846         } else {
7847             ClearHighlights();
7848         }
7849 #endif
7850         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7851           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7852         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7853         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7854           dragging *= 2;            // flag button-less dragging if we are dragging
7855           MarkTargetSquares(1);
7856           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7857           else {
7858             kill2X = killX; kill2Y = killY;
7859             killX = x; killY = y;     // remember this square as intermediate
7860             ReportClick("put", x, y); // and inform engine
7861             ReportClick("lift", x, y);
7862             MarkTargetSquares(0);
7863             return;
7864           }
7865         }
7866         DragPieceEnd(xPix, yPix); dragging = 0;
7867         /* Don't animate move and drag both */
7868         appData.animate = FALSE;
7869         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7870     }
7871
7872     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7873     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7874         ChessSquare piece = boards[currentMove][fromY][fromX];
7875         if(gameMode == EditPosition && piece != EmptySquare &&
7876            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7877             int n;
7878
7879             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7880                 n = PieceToNumber(piece - (int)BlackPawn);
7881                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7882                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7883                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7884             } else
7885             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7886                 n = PieceToNumber(piece);
7887                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7888                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7889                 boards[currentMove][n][BOARD_WIDTH-2]++;
7890             }
7891             boards[currentMove][fromY][fromX] = EmptySquare;
7892         }
7893         ClearHighlights();
7894         fromX = fromY = -1;
7895         MarkTargetSquares(1);
7896         DrawPosition(TRUE, boards[currentMove]);
7897         return;
7898     }
7899
7900     // off-board moves should not be highlighted
7901     if(x < 0 || y < 0) {
7902         ClearHighlights();
7903         DrawPosition(FALSE, NULL);
7904     } else ReportClick("put", x, y);
7905
7906     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7907  }
7908
7909     if(legal[toY][toX] == 2) { // highlight-induced promotion
7910         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7911         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7912     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7913       if(!*promoRestrict) {           // but has not done that yet
7914         deferChoice = TRUE;           // set up retry for when it does
7915         return;                       // and wait for that
7916       }
7917       promoChoice = ToLower(*promoRestrict); // force engine's choice
7918       deferChoice = FALSE;
7919     }
7920
7921     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7922         SetHighlights(fromX, fromY, toX, toY);
7923         MarkTargetSquares(1);
7924         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7925             // [HGM] super: promotion to captured piece selected from holdings
7926             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7927             promotionChoice = TRUE;
7928             // kludge follows to temporarily execute move on display, without promoting yet
7929             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7930             boards[currentMove][toY][toX] = p;
7931             DrawPosition(FALSE, boards[currentMove]);
7932             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7933             boards[currentMove][toY][toX] = q;
7934             DisplayMessage("Click in holdings to choose piece", "");
7935             return;
7936         }
7937         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7938         PromotionPopUp(promoChoice);
7939     } else {
7940         int oldMove = currentMove;
7941         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7942         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7943         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7944         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7945         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7946            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7947             DrawPosition(TRUE, boards[currentMove]);
7948         fromX = fromY = -1;
7949         flashing = 0;
7950     }
7951     appData.animate = saveAnimate;
7952     if (appData.animate || appData.animateDragging) {
7953         /* Undo animation damage if needed */
7954 //      DrawPosition(FALSE, NULL);
7955     }
7956 }
7957
7958 int
7959 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7960 {   // front-end-free part taken out of PieceMenuPopup
7961     int whichMenu; int xSqr, ySqr;
7962
7963     if(seekGraphUp) { // [HGM] seekgraph
7964         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7965         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7966         return -2;
7967     }
7968
7969     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7970          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7971         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7972         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7973         if(action == Press)   {
7974             originalFlip = flipView;
7975             flipView = !flipView; // temporarily flip board to see game from partners perspective
7976             DrawPosition(TRUE, partnerBoard);
7977             DisplayMessage(partnerStatus, "");
7978             partnerUp = TRUE;
7979         } else if(action == Release) {
7980             flipView = originalFlip;
7981             DrawPosition(TRUE, boards[currentMove]);
7982             partnerUp = FALSE;
7983         }
7984         return -2;
7985     }
7986
7987     xSqr = EventToSquare(x, BOARD_WIDTH);
7988     ySqr = EventToSquare(y, BOARD_HEIGHT);
7989     if (action == Release) {
7990         if(pieceSweep != EmptySquare) {
7991             EditPositionMenuEvent(pieceSweep, toX, toY);
7992             pieceSweep = EmptySquare;
7993         } else UnLoadPV(); // [HGM] pv
7994     }
7995     if (action != Press) return -2; // return code to be ignored
7996     switch (gameMode) {
7997       case IcsExamining:
7998         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7999       case EditPosition:
8000         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8001         if (xSqr < 0 || ySqr < 0) return -1;
8002         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8003         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8004         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8005         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8006         NextPiece(0);
8007         return 2; // grab
8008       case IcsObserving:
8009         if(!appData.icsEngineAnalyze) return -1;
8010       case IcsPlayingWhite:
8011       case IcsPlayingBlack:
8012         if(!appData.zippyPlay) goto noZip;
8013       case AnalyzeMode:
8014       case AnalyzeFile:
8015       case MachinePlaysWhite:
8016       case MachinePlaysBlack:
8017       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8018         if (!appData.dropMenu) {
8019           LoadPV(x, y);
8020           return 2; // flag front-end to grab mouse events
8021         }
8022         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8023            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8024       case EditGame:
8025       noZip:
8026         if (xSqr < 0 || ySqr < 0) return -1;
8027         if (!appData.dropMenu || appData.testLegality &&
8028             gameInfo.variant != VariantBughouse &&
8029             gameInfo.variant != VariantCrazyhouse) return -1;
8030         whichMenu = 1; // drop menu
8031         break;
8032       default:
8033         return -1;
8034     }
8035
8036     if (((*fromX = xSqr) < 0) ||
8037         ((*fromY = ySqr) < 0)) {
8038         *fromX = *fromY = -1;
8039         return -1;
8040     }
8041     if (flipView)
8042       *fromX = BOARD_WIDTH - 1 - *fromX;
8043     else
8044       *fromY = BOARD_HEIGHT - 1 - *fromY;
8045
8046     return whichMenu;
8047 }
8048
8049 void
8050 Wheel (int dir, int x, int y)
8051 {
8052     if(gameMode == EditPosition) {
8053         int xSqr = EventToSquare(x, BOARD_WIDTH);
8054         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8055         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8056         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8057         do {
8058             boards[currentMove][ySqr][xSqr] += dir;
8059             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8060             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8061         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8062         DrawPosition(FALSE, boards[currentMove]);
8063     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8064 }
8065
8066 void
8067 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8068 {
8069 //    char * hint = lastHint;
8070     FrontEndProgramStats stats;
8071
8072     stats.which = cps == &first ? 0 : 1;
8073     stats.depth = cpstats->depth;
8074     stats.nodes = cpstats->nodes;
8075     stats.score = cpstats->score;
8076     stats.time = cpstats->time;
8077     stats.pv = cpstats->movelist;
8078     stats.hint = lastHint;
8079     stats.an_move_index = 0;
8080     stats.an_move_count = 0;
8081
8082     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8083         stats.hint = cpstats->move_name;
8084         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8085         stats.an_move_count = cpstats->nr_moves;
8086     }
8087
8088     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
8089
8090     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8091         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8092
8093     SetProgramStats( &stats );
8094 }
8095
8096 void
8097 ClearEngineOutputPane (int which)
8098 {
8099     static FrontEndProgramStats dummyStats;
8100     dummyStats.which = which;
8101     dummyStats.pv = "#";
8102     SetProgramStats( &dummyStats );
8103 }
8104
8105 #define MAXPLAYERS 500
8106
8107 char *
8108 TourneyStandings (int display)
8109 {
8110     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8111     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8112     char result, *p, *names[MAXPLAYERS];
8113
8114     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8115         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8116     names[0] = p = strdup(appData.participants);
8117     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8118
8119     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8120
8121     while(result = appData.results[nr]) {
8122         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8123         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8124         wScore = bScore = 0;
8125         switch(result) {
8126           case '+': wScore = 2; break;
8127           case '-': bScore = 2; break;
8128           case '=': wScore = bScore = 1; break;
8129           case ' ':
8130           case '*': return strdup("busy"); // tourney not finished
8131         }
8132         score[w] += wScore;
8133         score[b] += bScore;
8134         games[w]++;
8135         games[b]++;
8136         nr++;
8137     }
8138     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8139     for(w=0; w<nPlayers; w++) {
8140         bScore = -1;
8141         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8142         ranking[w] = b; points[w] = bScore; score[b] = -2;
8143     }
8144     p = malloc(nPlayers*34+1);
8145     for(w=0; w<nPlayers && w<display; w++)
8146         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8147     free(names[0]);
8148     return p;
8149 }
8150
8151 void
8152 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8153 {       // count all piece types
8154         int p, f, r;
8155         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8156         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8157         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8158                 p = board[r][f];
8159                 pCnt[p]++;
8160                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8161                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8162                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8163                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8164                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8165                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8166         }
8167 }
8168
8169 int
8170 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8171 {
8172         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8173         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8174
8175         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8176         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8177         if(myPawns == 2 && nMine == 3) // KPP
8178             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8179         if(myPawns == 1 && nMine == 2) // KP
8180             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8181         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8182             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8183         if(myPawns) return FALSE;
8184         if(pCnt[WhiteRook+side])
8185             return pCnt[BlackRook-side] ||
8186                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8187                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8188                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8189         if(pCnt[WhiteCannon+side]) {
8190             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8191             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8192         }
8193         if(pCnt[WhiteKnight+side])
8194             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8195         return FALSE;
8196 }
8197
8198 int
8199 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8200 {
8201         VariantClass v = gameInfo.variant;
8202
8203         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8204         if(v == VariantShatranj) return TRUE; // always winnable through baring
8205         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8206         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8207
8208         if(v == VariantXiangqi) {
8209                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8210
8211                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8212                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8213                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8214                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8215                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8216                 if(stale) // we have at least one last-rank P plus perhaps C
8217                     return majors // KPKX
8218                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8219                 else // KCA*E*
8220                     return pCnt[WhiteFerz+side] // KCAK
8221                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8222                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8223                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8224
8225         } else if(v == VariantKnightmate) {
8226                 if(nMine == 1) return FALSE;
8227                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8228         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8229                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8230
8231                 if(nMine == 1) return FALSE; // bare King
8232                 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
8233                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8234                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8235                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8236                 if(pCnt[WhiteKnight+side])
8237                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8238                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8239                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8240                 if(nBishops)
8241                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8242                 if(pCnt[WhiteAlfil+side])
8243                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8244                 if(pCnt[WhiteWazir+side])
8245                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8246         }
8247
8248         return TRUE;
8249 }
8250
8251 int
8252 CompareWithRights (Board b1, Board b2)
8253 {
8254     int rights = 0;
8255     if(!CompareBoards(b1, b2)) return FALSE;
8256     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8257     /* compare castling rights */
8258     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8259            rights++; /* King lost rights, while rook still had them */
8260     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8261         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8262            rights++; /* but at least one rook lost them */
8263     }
8264     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8265            rights++;
8266     if( b1[CASTLING][5] != NoRights ) {
8267         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8268            rights++;
8269     }
8270     return rights == 0;
8271 }
8272
8273 int
8274 Adjudicate (ChessProgramState *cps)
8275 {       // [HGM] some adjudications useful with buggy engines
8276         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8277         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8278         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8279         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8280         int k, drop, count = 0; static int bare = 1;
8281         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8282         Boolean canAdjudicate = !appData.icsActive;
8283
8284         // most tests only when we understand the game, i.e. legality-checking on
8285             if( appData.testLegality )
8286             {   /* [HGM] Some more adjudications for obstinate engines */
8287                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8288                 static int moveCount = 6;
8289                 ChessMove result;
8290                 char *reason = NULL;
8291
8292                 /* Count what is on board. */
8293                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8294
8295                 /* Some material-based adjudications that have to be made before stalemate test */
8296                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8297                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8298                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8299                      if(canAdjudicate && appData.checkMates) {
8300                          if(engineOpponent)
8301                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8302                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8303                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8304                          return 1;
8305                      }
8306                 }
8307
8308                 /* Bare King in Shatranj (loses) or Losers (wins) */
8309                 if( nrW == 1 || nrB == 1) {
8310                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8311                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8312                      if(canAdjudicate && appData.checkMates) {
8313                          if(engineOpponent)
8314                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8315                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8316                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8317                          return 1;
8318                      }
8319                   } else
8320                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8321                   {    /* bare King */
8322                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8323                         if(canAdjudicate && appData.checkMates) {
8324                             /* but only adjudicate if adjudication enabled */
8325                             if(engineOpponent)
8326                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8327                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8328                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8329                             return 1;
8330                         }
8331                   }
8332                 } else bare = 1;
8333
8334
8335             // don't wait for engine to announce game end if we can judge ourselves
8336             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8337               case MT_CHECK:
8338                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8339                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8340                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8341                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8342                             checkCnt++;
8343                         if(checkCnt >= 2) {
8344                             reason = "Xboard adjudication: 3rd check";
8345                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8346                             break;
8347                         }
8348                     }
8349                 }
8350               case MT_NONE:
8351               default:
8352                 break;
8353               case MT_STEALMATE:
8354               case MT_STALEMATE:
8355               case MT_STAINMATE:
8356                 reason = "Xboard adjudication: Stalemate";
8357                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8358                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8359                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8360                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8361                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8362                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8363                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8364                                                                         EP_CHECKMATE : EP_WINS);
8365                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8366                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8367                 }
8368                 break;
8369               case MT_CHECKMATE:
8370                 reason = "Xboard adjudication: Checkmate";
8371                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8372                 if(gameInfo.variant == VariantShogi) {
8373                     if(forwardMostMove > backwardMostMove
8374                        && moveList[forwardMostMove-1][1] == '@'
8375                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8376                         reason = "XBoard adjudication: pawn-drop mate";
8377                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8378                     }
8379                 }
8380                 break;
8381             }
8382
8383                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8384                     case EP_STALEMATE:
8385                         result = GameIsDrawn; break;
8386                     case EP_CHECKMATE:
8387                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8388                     case EP_WINS:
8389                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8390                     default:
8391                         result = EndOfFile;
8392                 }
8393                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8394                     if(engineOpponent)
8395                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8396                     GameEnds( result, reason, GE_XBOARD );
8397                     return 1;
8398                 }
8399
8400                 /* Next absolutely insufficient mating material. */
8401                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8402                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8403                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8404
8405                      /* always flag draws, for judging claims */
8406                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8407
8408                      if(canAdjudicate && appData.materialDraws) {
8409                          /* but only adjudicate them if adjudication enabled */
8410                          if(engineOpponent) {
8411                            SendToProgram("force\n", engineOpponent); // suppress reply
8412                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8413                          }
8414                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8415                          return 1;
8416                      }
8417                 }
8418
8419                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8420                 if(gameInfo.variant == VariantXiangqi ?
8421                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8422                  : nrW + nrB == 4 &&
8423                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8424                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8425                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8426                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8427                    ) ) {
8428                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8429                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8430                           if(engineOpponent) {
8431                             SendToProgram("force\n", engineOpponent); // suppress reply
8432                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8433                           }
8434                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8435                           return 1;
8436                      }
8437                 } else moveCount = 6;
8438             }
8439
8440         // Repetition draws and 50-move rule can be applied independently of legality testing
8441
8442                 /* Check for rep-draws */
8443                 count = 0;
8444                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8445                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8446                 for(k = forwardMostMove-2;
8447                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8448                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8449                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8450                     k-=2)
8451                 {   int rights=0;
8452                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8453                         /* compare castling rights */
8454                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8455                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8456                                 rights++; /* King lost rights, while rook still had them */
8457                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8458                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8459                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8460                                    rights++; /* but at least one rook lost them */
8461                         }
8462                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8463                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8464                                 rights++;
8465                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8466                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8467                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8468                                    rights++;
8469                         }
8470                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8471                             && appData.drawRepeats > 1) {
8472                              /* adjudicate after user-specified nr of repeats */
8473                              int result = GameIsDrawn;
8474                              char *details = "XBoard adjudication: repetition draw";
8475                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8476                                 // [HGM] xiangqi: check for forbidden perpetuals
8477                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8478                                 for(m=forwardMostMove; m>k; m-=2) {
8479                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8480                                         ourPerpetual = 0; // the current mover did not always check
8481                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8482                                         hisPerpetual = 0; // the opponent did not always check
8483                                 }
8484                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8485                                                                         ourPerpetual, hisPerpetual);
8486                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8487                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8488                                     details = "Xboard adjudication: perpetual checking";
8489                                 } else
8490                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8491                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8492                                 } else
8493                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8494                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8495                                         result = BlackWins;
8496                                         details = "Xboard adjudication: repetition";
8497                                     }
8498                                 } else // it must be XQ
8499                                 // Now check for perpetual chases
8500                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8501                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8502                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8503                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8504                                         static char resdet[MSG_SIZ];
8505                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8506                                         details = resdet;
8507                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8508                                     } else
8509                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8510                                         break; // Abort repetition-checking loop.
8511                                 }
8512                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8513                              }
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( result, details, GE_XBOARD );
8519                              return 1;
8520                         }
8521                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8522                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8523                     }
8524                 }
8525
8526                 /* Now we test for 50-move draws. Determine ply count */
8527                 count = forwardMostMove;
8528                 /* look for last irreversble move */
8529                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8530                     count--;
8531                 /* if we hit starting position, add initial plies */
8532                 if( count == backwardMostMove )
8533                     count -= initialRulePlies;
8534                 count = forwardMostMove - count;
8535                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8536                         // adjust reversible move counter for checks in Xiangqi
8537                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8538                         if(i < backwardMostMove) i = backwardMostMove;
8539                         while(i <= forwardMostMove) {
8540                                 lastCheck = inCheck; // check evasion does not count
8541                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8542                                 if(inCheck || lastCheck) count--; // check does not count
8543                                 i++;
8544                         }
8545                 }
8546                 if( count >= 100)
8547                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8548                          /* this is used to judge if draw claims are legal */
8549                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8550                          if(engineOpponent) {
8551                            SendToProgram("force\n", engineOpponent); // suppress reply
8552                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8553                          }
8554                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8555                          return 1;
8556                 }
8557
8558                 /* if draw offer is pending, treat it as a draw claim
8559                  * when draw condition present, to allow engines a way to
8560                  * claim draws before making their move to avoid a race
8561                  * condition occurring after their move
8562                  */
8563                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8564                          char *p = NULL;
8565                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8566                              p = "Draw claim: 50-move rule";
8567                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8568                              p = "Draw claim: 3-fold repetition";
8569                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8570                              p = "Draw claim: insufficient mating material";
8571                          if( p != NULL && canAdjudicate) {
8572                              if(engineOpponent) {
8573                                SendToProgram("force\n", engineOpponent); // suppress reply
8574                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8575                              }
8576                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8577                              return 1;
8578                          }
8579                 }
8580
8581                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8582                     if(engineOpponent) {
8583                       SendToProgram("force\n", engineOpponent); // suppress reply
8584                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8585                     }
8586                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8587                     return 1;
8588                 }
8589         return 0;
8590 }
8591
8592 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8593 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8594 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8595
8596 static int
8597 BitbaseProbe ()
8598 {
8599     int pieces[10], squares[10], cnt=0, r, f, res;
8600     static int loaded;
8601     static PPROBE_EGBB probeBB;
8602     if(!appData.testLegality) return 10;
8603     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8604     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8605     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8606     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8607         ChessSquare piece = boards[forwardMostMove][r][f];
8608         int black = (piece >= BlackPawn);
8609         int type = piece - black*BlackPawn;
8610         if(piece == EmptySquare) continue;
8611         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8612         if(type == WhiteKing) type = WhiteQueen + 1;
8613         type = egbbCode[type];
8614         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8615         pieces[cnt] = type + black*6;
8616         if(++cnt > 5) return 11;
8617     }
8618     pieces[cnt] = squares[cnt] = 0;
8619     // probe EGBB
8620     if(loaded == 2) return 13; // loading failed before
8621     if(loaded == 0) {
8622         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8623         HMODULE lib;
8624         PLOAD_EGBB loadBB;
8625         loaded = 2; // prepare for failure
8626         if(!path) return 13; // no egbb installed
8627         strncpy(buf, path + 8, MSG_SIZ);
8628         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8629         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8630         lib = LoadLibrary(buf);
8631         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8632         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8633         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8634         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8635         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8636         loaded = 1; // success!
8637     }
8638     res = probeBB(forwardMostMove & 1, pieces, squares);
8639     return res > 0 ? 1 : res < 0 ? -1 : 0;
8640 }
8641
8642 char *
8643 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8644 {   // [HGM] book: this routine intercepts moves to simulate book replies
8645     char *bookHit = NULL;
8646
8647     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8648         char buf[MSG_SIZ];
8649         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8650         SendToProgram(buf, cps);
8651     }
8652     //first determine if the incoming move brings opponent into his book
8653     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8654         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8655     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8656     if(bookHit != NULL && !cps->bookSuspend) {
8657         // make sure opponent is not going to reply after receiving move to book position
8658         SendToProgram("force\n", cps);
8659         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8660     }
8661     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8662     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8663     // now arrange restart after book miss
8664     if(bookHit) {
8665         // after a book hit we never send 'go', and the code after the call to this routine
8666         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8667         char buf[MSG_SIZ], *move = bookHit;
8668         if(cps->useSAN) {
8669             int fromX, fromY, toX, toY;
8670             char promoChar;
8671             ChessMove moveType;
8672             move = buf + 30;
8673             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8674                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8675                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8676                                     PosFlags(forwardMostMove),
8677                                     fromY, fromX, toY, toX, promoChar, move);
8678             } else {
8679                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8680                 bookHit = NULL;
8681             }
8682         }
8683         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8684         SendToProgram(buf, cps);
8685         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8686     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8687         SendToProgram("go\n", cps);
8688         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8689     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8690         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8691             SendToProgram("go\n", cps);
8692         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8693     }
8694     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8695 }
8696
8697 int
8698 LoadError (char *errmess, ChessProgramState *cps)
8699 {   // unloads engine and switches back to -ncp mode if it was first
8700     if(cps->initDone) return FALSE;
8701     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8702     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8703     cps->pr = NoProc;
8704     if(cps == &first) {
8705         appData.noChessProgram = TRUE;
8706         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8707         gameMode = BeginningOfGame; ModeHighlight();
8708         SetNCPMode();
8709     }
8710     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8711     DisplayMessage("", ""); // erase waiting message
8712     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8713     return TRUE;
8714 }
8715
8716 char *savedMessage;
8717 ChessProgramState *savedState;
8718 void
8719 DeferredBookMove (void)
8720 {
8721         if(savedState->lastPing != savedState->lastPong)
8722                     ScheduleDelayedEvent(DeferredBookMove, 10);
8723         else
8724         HandleMachineMove(savedMessage, savedState);
8725 }
8726
8727 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8728 static ChessProgramState *stalledEngine;
8729 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8730
8731 void
8732 HandleMachineMove (char *message, ChessProgramState *cps)
8733 {
8734     static char firstLeg[20], legs;
8735     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8736     char realname[MSG_SIZ];
8737     int fromX, fromY, toX, toY;
8738     ChessMove moveType;
8739     char promoChar, roar;
8740     char *p, *pv=buf1;
8741     int oldError;
8742     char *bookHit;
8743
8744     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8745         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8746         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8747             DisplayError(_("Invalid pairing from pairing engine"), 0);
8748             return;
8749         }
8750         pairingReceived = 1;
8751         NextMatchGame();
8752         return; // Skim the pairing messages here.
8753     }
8754
8755     oldError = cps->userError; cps->userError = 0;
8756
8757 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8758     /*
8759      * Kludge to ignore BEL characters
8760      */
8761     while (*message == '\007') message++;
8762
8763     /*
8764      * [HGM] engine debug message: ignore lines starting with '#' character
8765      */
8766     if(cps->debug && *message == '#') return;
8767
8768     /*
8769      * Look for book output
8770      */
8771     if (cps == &first && bookRequested) {
8772         if (message[0] == '\t' || message[0] == ' ') {
8773             /* Part of the book output is here; append it */
8774             strcat(bookOutput, message);
8775             strcat(bookOutput, "  \n");
8776             return;
8777         } else if (bookOutput[0] != NULLCHAR) {
8778             /* All of book output has arrived; display it */
8779             char *p = bookOutput;
8780             while (*p != NULLCHAR) {
8781                 if (*p == '\t') *p = ' ';
8782                 p++;
8783             }
8784             DisplayInformation(bookOutput);
8785             bookRequested = FALSE;
8786             /* Fall through to parse the current output */
8787         }
8788     }
8789
8790     /*
8791      * Look for machine move.
8792      */
8793     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8794         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8795     {
8796         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8797             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8798             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8799             stalledEngine = cps;
8800             if(appData.ponderNextMove) { // bring opponent out of ponder
8801                 if(gameMode == TwoMachinesPlay) {
8802                     if(cps->other->pause)
8803                         PauseEngine(cps->other);
8804                     else
8805                         SendToProgram("easy\n", cps->other);
8806                 }
8807             }
8808             StopClocks();
8809             return;
8810         }
8811
8812       if(cps->usePing) {
8813
8814         /* This method is only useful on engines that support ping */
8815         if(abortEngineThink) {
8816             if (appData.debugMode) {
8817                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8818             }
8819             SendToProgram("undo\n", cps);
8820             return;
8821         }
8822
8823         if (cps->lastPing != cps->lastPong) {
8824             /* Extra move from before last new; ignore */
8825             if (appData.debugMode) {
8826                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8827             }
8828           return;
8829         }
8830
8831       } else {
8832
8833         int machineWhite = FALSE;
8834
8835         switch (gameMode) {
8836           case BeginningOfGame:
8837             /* Extra move from before last reset; ignore */
8838             if (appData.debugMode) {
8839                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8840             }
8841             return;
8842
8843           case EndOfGame:
8844           case IcsIdle:
8845           default:
8846             /* Extra move after we tried to stop.  The mode test is
8847                not a reliable way of detecting this problem, but it's
8848                the best we can do on engines that don't support ping.
8849             */
8850             if (appData.debugMode) {
8851                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8852                         cps->which, gameMode);
8853             }
8854             SendToProgram("undo\n", cps);
8855             return;
8856
8857           case MachinePlaysWhite:
8858           case IcsPlayingWhite:
8859             machineWhite = TRUE;
8860             break;
8861
8862           case MachinePlaysBlack:
8863           case IcsPlayingBlack:
8864             machineWhite = FALSE;
8865             break;
8866
8867           case TwoMachinesPlay:
8868             machineWhite = (cps->twoMachinesColor[0] == 'w');
8869             break;
8870         }
8871         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8872             if (appData.debugMode) {
8873                 fprintf(debugFP,
8874                         "Ignoring move out of turn by %s, gameMode %d"
8875                         ", forwardMost %d\n",
8876                         cps->which, gameMode, forwardMostMove);
8877             }
8878             return;
8879         }
8880       }
8881
8882         if(cps->alphaRank) AlphaRank(machineMove, 4);
8883
8884         // [HGM] lion: (some very limited) support for Alien protocol
8885         killX = killY = kill2X = kill2Y = -1;
8886         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8887             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8888             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8889             return;
8890         }
8891         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8892             char *q = strchr(p+1, ',');            // second comma?
8893             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8894             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8895             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8896         }
8897         if(firstLeg[0]) { // there was a previous leg;
8898             // only support case where same piece makes two step
8899             char buf[20], *p = machineMove+1, *q = buf+1, f;
8900             safeStrCpy(buf, machineMove, 20);
8901             while(isdigit(*q)) q++; // find start of to-square
8902             safeStrCpy(machineMove, firstLeg, 20);
8903             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8904             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8905             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8906             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8907             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8908             firstLeg[0] = NULLCHAR; legs = 0;
8909         }
8910
8911         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8912                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8913             /* Machine move could not be parsed; ignore it. */
8914           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8915                     machineMove, _(cps->which));
8916             DisplayMoveError(buf1);
8917             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8918                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8919             if (gameMode == TwoMachinesPlay) {
8920               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8921                        buf1, GE_XBOARD);
8922             }
8923             return;
8924         }
8925
8926         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8927         /* So we have to redo legality test with true e.p. status here,  */
8928         /* to make sure an illegal e.p. capture does not slip through,   */
8929         /* to cause a forfeit on a justified illegal-move complaint      */
8930         /* of the opponent.                                              */
8931         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8932            ChessMove moveType;
8933            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8934                              fromY, fromX, toY, toX, promoChar);
8935             if(moveType == IllegalMove) {
8936               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8937                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8938                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8939                            buf1, GE_XBOARD);
8940                 return;
8941            } else if(!appData.fischerCastling)
8942            /* [HGM] Kludge to handle engines that send FRC-style castling
8943               when they shouldn't (like TSCP-Gothic) */
8944            switch(moveType) {
8945              case WhiteASideCastleFR:
8946              case BlackASideCastleFR:
8947                toX+=2;
8948                currentMoveString[2]++;
8949                break;
8950              case WhiteHSideCastleFR:
8951              case BlackHSideCastleFR:
8952                toX--;
8953                currentMoveString[2]--;
8954                break;
8955              default: ; // nothing to do, but suppresses warning of pedantic compilers
8956            }
8957         }
8958         hintRequested = FALSE;
8959         lastHint[0] = NULLCHAR;
8960         bookRequested = FALSE;
8961         /* Program may be pondering now */
8962         cps->maybeThinking = TRUE;
8963         if (cps->sendTime == 2) cps->sendTime = 1;
8964         if (cps->offeredDraw) cps->offeredDraw--;
8965
8966         /* [AS] Save move info*/
8967         pvInfoList[ forwardMostMove ].score = programStats.score;
8968         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8969         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8970
8971         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8972
8973         /* Test suites abort the 'game' after one move */
8974         if(*appData.finger) {
8975            static FILE *f;
8976            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8977            if(!f) f = fopen(appData.finger, "w");
8978            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8979            else { DisplayFatalError("Bad output file", errno, 0); return; }
8980            free(fen);
8981            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8982         }
8983         if(appData.epd) {
8984            if(solvingTime >= 0) {
8985               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8986               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8987            } else {
8988               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8989               if(solvingTime == -2) second.matchWins++;
8990            }
8991            OutputKibitz(2, buf1);
8992            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8993         }
8994
8995         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8996         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8997             int count = 0;
8998
8999             while( count < adjudicateLossPlies ) {
9000                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9001
9002                 if( count & 1 ) {
9003                     score = -score; /* Flip score for winning side */
9004                 }
9005
9006                 if( score > appData.adjudicateLossThreshold ) {
9007                     break;
9008                 }
9009
9010                 count++;
9011             }
9012
9013             if( count >= adjudicateLossPlies ) {
9014                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9015
9016                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9017                     "Xboard adjudication",
9018                     GE_XBOARD );
9019
9020                 return;
9021             }
9022         }
9023
9024         if(Adjudicate(cps)) {
9025             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9026             return; // [HGM] adjudicate: for all automatic game ends
9027         }
9028
9029 #if ZIPPY
9030         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9031             first.initDone) {
9032           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9033                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9034                 SendToICS("draw ");
9035                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9036           }
9037           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9038           ics_user_moved = 1;
9039           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9040                 char buf[3*MSG_SIZ];
9041
9042                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9043                         programStats.score / 100.,
9044                         programStats.depth,
9045                         programStats.time / 100.,
9046                         (unsigned int)programStats.nodes,
9047                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9048                         programStats.movelist);
9049                 SendToICS(buf);
9050           }
9051         }
9052 #endif
9053
9054         /* [AS] Clear stats for next move */
9055         ClearProgramStats();
9056         thinkOutput[0] = NULLCHAR;
9057         hiddenThinkOutputState = 0;
9058
9059         bookHit = NULL;
9060         if (gameMode == TwoMachinesPlay) {
9061             /* [HGM] relaying draw offers moved to after reception of move */
9062             /* and interpreting offer as claim if it brings draw condition */
9063             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9064                 SendToProgram("draw\n", cps->other);
9065             }
9066             if (cps->other->sendTime) {
9067                 SendTimeRemaining(cps->other,
9068                                   cps->other->twoMachinesColor[0] == 'w');
9069             }
9070             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9071             if (firstMove && !bookHit) {
9072                 firstMove = FALSE;
9073                 if (cps->other->useColors) {
9074                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9075                 }
9076                 SendToProgram("go\n", cps->other);
9077             }
9078             cps->other->maybeThinking = TRUE;
9079         }
9080
9081         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9082
9083         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9084
9085         if (!pausing && appData.ringBellAfterMoves) {
9086             if(!roar) RingBell();
9087         }
9088
9089         /*
9090          * Reenable menu items that were disabled while
9091          * machine was thinking
9092          */
9093         if (gameMode != TwoMachinesPlay)
9094             SetUserThinkingEnables();
9095
9096         // [HGM] book: after book hit opponent has received move and is now in force mode
9097         // force the book reply into it, and then fake that it outputted this move by jumping
9098         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9099         if(bookHit) {
9100                 static char bookMove[MSG_SIZ]; // a bit generous?
9101
9102                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9103                 strcat(bookMove, bookHit);
9104                 message = bookMove;
9105                 cps = cps->other;
9106                 programStats.nodes = programStats.depth = programStats.time =
9107                 programStats.score = programStats.got_only_move = 0;
9108                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9109
9110                 if(cps->lastPing != cps->lastPong) {
9111                     savedMessage = message; // args for deferred call
9112                     savedState = cps;
9113                     ScheduleDelayedEvent(DeferredBookMove, 10);
9114                     return;
9115                 }
9116                 goto FakeBookMove;
9117         }
9118
9119         return;
9120     }
9121
9122     /* Set special modes for chess engines.  Later something general
9123      *  could be added here; for now there is just one kludge feature,
9124      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9125      *  when "xboard" is given as an interactive command.
9126      */
9127     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9128         cps->useSigint = FALSE;
9129         cps->useSigterm = FALSE;
9130     }
9131     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9132       ParseFeatures(message+8, cps);
9133       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9134     }
9135
9136     if (!strncmp(message, "setup ", 6) && 
9137         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9138           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9139                                         ) { // [HGM] allow first engine to define opening position
9140       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9141       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9142       *buf = NULLCHAR;
9143       if(sscanf(message, "setup (%s", buf) == 1) {
9144         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9145         ASSIGN(appData.pieceToCharTable, buf);
9146       }
9147       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9148       if(dummy >= 3) {
9149         while(message[s] && message[s++] != ' ');
9150         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9151            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9152             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9153             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9154             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9155           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9156           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9157           startedFromSetupPosition = FALSE;
9158         }
9159       }
9160       if(startedFromSetupPosition) return;
9161       ParseFEN(boards[0], &dummy, message+s, FALSE);
9162       DrawPosition(TRUE, boards[0]);
9163       CopyBoard(initialPosition, boards[0]);
9164       startedFromSetupPosition = TRUE;
9165       return;
9166     }
9167     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9168       ChessSquare piece = WhitePawn;
9169       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9170       if(*p == '+') promoted++, ID = *++p;
9171       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9172       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9173       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9174       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9175       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9176       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9177       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9178       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9179                                                && gameInfo.variant != VariantGreat
9180                                                && gameInfo.variant != VariantFairy    ) return;
9181       if(piece < EmptySquare) {
9182         pieceDefs = TRUE;
9183         ASSIGN(pieceDesc[piece], buf1);
9184         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9185       }
9186       return;
9187     }
9188     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9189       if(deferChoice) {
9190         LeftClick(Press, 0, 0); // finish the click that was interrupted
9191       } else if(promoSweep != EmptySquare) {
9192         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9193         if(strlen(promoRestrict) > 1) Sweep(0);
9194       }
9195       return;
9196     }
9197     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9198      * want this, I was asked to put it in, and obliged.
9199      */
9200     if (!strncmp(message, "setboard ", 9)) {
9201         Board initial_position;
9202
9203         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9204
9205         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9206             DisplayError(_("Bad FEN received from engine"), 0);
9207             return ;
9208         } else {
9209            Reset(TRUE, FALSE);
9210            CopyBoard(boards[0], initial_position);
9211            initialRulePlies = FENrulePlies;
9212            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9213            else gameMode = MachinePlaysBlack;
9214            DrawPosition(FALSE, boards[currentMove]);
9215         }
9216         return;
9217     }
9218
9219     /*
9220      * Look for communication commands
9221      */
9222     if (!strncmp(message, "telluser ", 9)) {
9223         if(message[9] == '\\' && message[10] == '\\')
9224             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9225         PlayTellSound();
9226         DisplayNote(message + 9);
9227         return;
9228     }
9229     if (!strncmp(message, "tellusererror ", 14)) {
9230         cps->userError = 1;
9231         if(message[14] == '\\' && message[15] == '\\')
9232             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9233         PlayTellSound();
9234         DisplayError(message + 14, 0);
9235         return;
9236     }
9237     if (!strncmp(message, "tellopponent ", 13)) {
9238       if (appData.icsActive) {
9239         if (loggedOn) {
9240           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9241           SendToICS(buf1);
9242         }
9243       } else {
9244         DisplayNote(message + 13);
9245       }
9246       return;
9247     }
9248     if (!strncmp(message, "tellothers ", 11)) {
9249       if (appData.icsActive) {
9250         if (loggedOn) {
9251           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9252           SendToICS(buf1);
9253         }
9254       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9255       return;
9256     }
9257     if (!strncmp(message, "tellall ", 8)) {
9258       if (appData.icsActive) {
9259         if (loggedOn) {
9260           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9261           SendToICS(buf1);
9262         }
9263       } else {
9264         DisplayNote(message + 8);
9265       }
9266       return;
9267     }
9268     if (strncmp(message, "warning", 7) == 0) {
9269         /* Undocumented feature, use tellusererror in new code */
9270         DisplayError(message, 0);
9271         return;
9272     }
9273     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9274         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9275         strcat(realname, " query");
9276         AskQuestion(realname, buf2, buf1, cps->pr);
9277         return;
9278     }
9279     /* Commands from the engine directly to ICS.  We don't allow these to be
9280      *  sent until we are logged on. Crafty kibitzes have been known to
9281      *  interfere with the login process.
9282      */
9283     if (loggedOn) {
9284         if (!strncmp(message, "tellics ", 8)) {
9285             SendToICS(message + 8);
9286             SendToICS("\n");
9287             return;
9288         }
9289         if (!strncmp(message, "tellicsnoalias ", 15)) {
9290             SendToICS(ics_prefix);
9291             SendToICS(message + 15);
9292             SendToICS("\n");
9293             return;
9294         }
9295         /* The following are for backward compatibility only */
9296         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9297             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9298             SendToICS(ics_prefix);
9299             SendToICS(message);
9300             SendToICS("\n");
9301             return;
9302         }
9303     }
9304     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9305         if(initPing == cps->lastPong) {
9306             if(gameInfo.variant == VariantUnknown) {
9307                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9308                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9309                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9310             }
9311             initPing = -1;
9312         }
9313         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9314             abortEngineThink = FALSE;
9315             DisplayMessage("", "");
9316             ThawUI();
9317         }
9318         return;
9319     }
9320     if(!strncmp(message, "highlight ", 10)) {
9321         if(appData.testLegality && !*engineVariant && appData.markers) return;
9322         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9323         return;
9324     }
9325     if(!strncmp(message, "click ", 6)) {
9326         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9327         if(appData.testLegality || !appData.oneClick) return;
9328         sscanf(message+6, "%c%d%c", &f, &y, &c);
9329         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9330         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9331         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9332         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9333         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9334         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9335             LeftClick(Release, lastLeftX, lastLeftY);
9336         controlKey  = (c == ',');
9337         LeftClick(Press, x, y);
9338         LeftClick(Release, x, y);
9339         first.highlight = f;
9340         return;
9341     }
9342     /*
9343      * If the move is illegal, cancel it and redraw the board.
9344      * Also deal with other error cases.  Matching is rather loose
9345      * here to accommodate engines written before the spec.
9346      */
9347     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9348         strncmp(message, "Error", 5) == 0) {
9349         if (StrStr(message, "name") ||
9350             StrStr(message, "rating") || StrStr(message, "?") ||
9351             StrStr(message, "result") || StrStr(message, "board") ||
9352             StrStr(message, "bk") || StrStr(message, "computer") ||
9353             StrStr(message, "variant") || StrStr(message, "hint") ||
9354             StrStr(message, "random") || StrStr(message, "depth") ||
9355             StrStr(message, "accepted")) {
9356             return;
9357         }
9358         if (StrStr(message, "protover")) {
9359           /* Program is responding to input, so it's apparently done
9360              initializing, and this error message indicates it is
9361              protocol version 1.  So we don't need to wait any longer
9362              for it to initialize and send feature commands. */
9363           FeatureDone(cps, 1);
9364           cps->protocolVersion = 1;
9365           return;
9366         }
9367         cps->maybeThinking = FALSE;
9368
9369         if (StrStr(message, "draw")) {
9370             /* Program doesn't have "draw" command */
9371             cps->sendDrawOffers = 0;
9372             return;
9373         }
9374         if (cps->sendTime != 1 &&
9375             (StrStr(message, "time") || StrStr(message, "otim"))) {
9376           /* Program apparently doesn't have "time" or "otim" command */
9377           cps->sendTime = 0;
9378           return;
9379         }
9380         if (StrStr(message, "analyze")) {
9381             cps->analysisSupport = FALSE;
9382             cps->analyzing = FALSE;
9383 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9384             EditGameEvent(); // [HGM] try to preserve loaded game
9385             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9386             DisplayError(buf2, 0);
9387             return;
9388         }
9389         if (StrStr(message, "(no matching move)st")) {
9390           /* Special kludge for GNU Chess 4 only */
9391           cps->stKludge = TRUE;
9392           SendTimeControl(cps, movesPerSession, timeControl,
9393                           timeIncrement, appData.searchDepth,
9394                           searchTime);
9395           return;
9396         }
9397         if (StrStr(message, "(no matching move)sd")) {
9398           /* Special kludge for GNU Chess 4 only */
9399           cps->sdKludge = TRUE;
9400           SendTimeControl(cps, movesPerSession, timeControl,
9401                           timeIncrement, appData.searchDepth,
9402                           searchTime);
9403           return;
9404         }
9405         if (!StrStr(message, "llegal")) {
9406             return;
9407         }
9408         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9409             gameMode == IcsIdle) return;
9410         if (forwardMostMove <= backwardMostMove) return;
9411         if (pausing) PauseEvent();
9412       if(appData.forceIllegal) {
9413             // [HGM] illegal: machine refused move; force position after move into it
9414           SendToProgram("force\n", cps);
9415           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9416                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9417                 // when black is to move, while there might be nothing on a2 or black
9418                 // might already have the move. So send the board as if white has the move.
9419                 // But first we must change the stm of the engine, as it refused the last move
9420                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9421                 if(WhiteOnMove(forwardMostMove)) {
9422                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9423                     SendBoard(cps, forwardMostMove); // kludgeless board
9424                 } else {
9425                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9426                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9427                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9428                 }
9429           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9430             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9431                  gameMode == TwoMachinesPlay)
9432               SendToProgram("go\n", cps);
9433             return;
9434       } else
9435         if (gameMode == PlayFromGameFile) {
9436             /* Stop reading this game file */
9437             gameMode = EditGame;
9438             ModeHighlight();
9439         }
9440         /* [HGM] illegal-move claim should forfeit game when Xboard */
9441         /* only passes fully legal moves                            */
9442         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9443             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9444                                 "False illegal-move claim", GE_XBOARD );
9445             return; // do not take back move we tested as valid
9446         }
9447         currentMove = forwardMostMove-1;
9448         DisplayMove(currentMove-1); /* before DisplayMoveError */
9449         SwitchClocks(forwardMostMove-1); // [HGM] race
9450         DisplayBothClocks();
9451         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9452                 parseList[currentMove], _(cps->which));
9453         DisplayMoveError(buf1);
9454         DrawPosition(FALSE, boards[currentMove]);
9455
9456         SetUserThinkingEnables();
9457         return;
9458     }
9459     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9460         /* Program has a broken "time" command that
9461            outputs a string not ending in newline.
9462            Don't use it. */
9463         cps->sendTime = 0;
9464     }
9465     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9466         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9467             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9468     }
9469
9470     /*
9471      * If chess program startup fails, exit with an error message.
9472      * Attempts to recover here are futile. [HGM] Well, we try anyway
9473      */
9474     if ((StrStr(message, "unknown host") != NULL)
9475         || (StrStr(message, "No remote directory") != NULL)
9476         || (StrStr(message, "not found") != NULL)
9477         || (StrStr(message, "No such file") != NULL)
9478         || (StrStr(message, "can't alloc") != NULL)
9479         || (StrStr(message, "Permission denied") != NULL)) {
9480
9481         cps->maybeThinking = FALSE;
9482         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9483                 _(cps->which), cps->program, cps->host, message);
9484         RemoveInputSource(cps->isr);
9485         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9486             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9487             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9488         }
9489         return;
9490     }
9491
9492     /*
9493      * Look for hint output
9494      */
9495     if (sscanf(message, "Hint: %s", buf1) == 1) {
9496         if (cps == &first && hintRequested) {
9497             hintRequested = FALSE;
9498             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9499                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9500                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9501                                     PosFlags(forwardMostMove),
9502                                     fromY, fromX, toY, toX, promoChar, buf1);
9503                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9504                 DisplayInformation(buf2);
9505             } else {
9506                 /* Hint move could not be parsed!? */
9507               snprintf(buf2, sizeof(buf2),
9508                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9509                         buf1, _(cps->which));
9510                 DisplayError(buf2, 0);
9511             }
9512         } else {
9513           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9514         }
9515         return;
9516     }
9517
9518     /*
9519      * Ignore other messages if game is not in progress
9520      */
9521     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9522         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9523
9524     /*
9525      * look for win, lose, draw, or draw offer
9526      */
9527     if (strncmp(message, "1-0", 3) == 0) {
9528         char *p, *q, *r = "";
9529         p = strchr(message, '{');
9530         if (p) {
9531             q = strchr(p, '}');
9532             if (q) {
9533                 *q = NULLCHAR;
9534                 r = p + 1;
9535             }
9536         }
9537         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9538         return;
9539     } else if (strncmp(message, "0-1", 3) == 0) {
9540         char *p, *q, *r = "";
9541         p = strchr(message, '{');
9542         if (p) {
9543             q = strchr(p, '}');
9544             if (q) {
9545                 *q = NULLCHAR;
9546                 r = p + 1;
9547             }
9548         }
9549         /* Kludge for Arasan 4.1 bug */
9550         if (strcmp(r, "Black resigns") == 0) {
9551             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9552             return;
9553         }
9554         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9555         return;
9556     } else if (strncmp(message, "1/2", 3) == 0) {
9557         char *p, *q, *r = "";
9558         p = strchr(message, '{');
9559         if (p) {
9560             q = strchr(p, '}');
9561             if (q) {
9562                 *q = NULLCHAR;
9563                 r = p + 1;
9564             }
9565         }
9566
9567         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9568         return;
9569
9570     } else if (strncmp(message, "White resign", 12) == 0) {
9571         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9572         return;
9573     } else if (strncmp(message, "Black resign", 12) == 0) {
9574         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9575         return;
9576     } else if (strncmp(message, "White matches", 13) == 0 ||
9577                strncmp(message, "Black matches", 13) == 0   ) {
9578         /* [HGM] ignore GNUShogi noises */
9579         return;
9580     } else if (strncmp(message, "White", 5) == 0 &&
9581                message[5] != '(' &&
9582                StrStr(message, "Black") == NULL) {
9583         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9584         return;
9585     } else if (strncmp(message, "Black", 5) == 0 &&
9586                message[5] != '(') {
9587         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9588         return;
9589     } else if (strcmp(message, "resign") == 0 ||
9590                strcmp(message, "computer resigns") == 0) {
9591         switch (gameMode) {
9592           case MachinePlaysBlack:
9593           case IcsPlayingBlack:
9594             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9595             break;
9596           case MachinePlaysWhite:
9597           case IcsPlayingWhite:
9598             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9599             break;
9600           case TwoMachinesPlay:
9601             if (cps->twoMachinesColor[0] == 'w')
9602               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9603             else
9604               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9605             break;
9606           default:
9607             /* can't happen */
9608             break;
9609         }
9610         return;
9611     } else if (strncmp(message, "opponent mates", 14) == 0) {
9612         switch (gameMode) {
9613           case MachinePlaysBlack:
9614           case IcsPlayingBlack:
9615             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9616             break;
9617           case MachinePlaysWhite:
9618           case IcsPlayingWhite:
9619             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9620             break;
9621           case TwoMachinesPlay:
9622             if (cps->twoMachinesColor[0] == 'w')
9623               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9624             else
9625               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9626             break;
9627           default:
9628             /* can't happen */
9629             break;
9630         }
9631         return;
9632     } else if (strncmp(message, "computer mates", 14) == 0) {
9633         switch (gameMode) {
9634           case MachinePlaysBlack:
9635           case IcsPlayingBlack:
9636             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9637             break;
9638           case MachinePlaysWhite:
9639           case IcsPlayingWhite:
9640             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9641             break;
9642           case TwoMachinesPlay:
9643             if (cps->twoMachinesColor[0] == 'w')
9644               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9645             else
9646               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9647             break;
9648           default:
9649             /* can't happen */
9650             break;
9651         }
9652         return;
9653     } else if (strncmp(message, "checkmate", 9) == 0) {
9654         if (WhiteOnMove(forwardMostMove)) {
9655             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9656         } else {
9657             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9658         }
9659         return;
9660     } else if (strstr(message, "Draw") != NULL ||
9661                strstr(message, "game is a draw") != NULL) {
9662         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9663         return;
9664     } else if (strstr(message, "offer") != NULL &&
9665                strstr(message, "draw") != NULL) {
9666 #if ZIPPY
9667         if (appData.zippyPlay && first.initDone) {
9668             /* Relay offer to ICS */
9669             SendToICS(ics_prefix);
9670             SendToICS("draw\n");
9671         }
9672 #endif
9673         cps->offeredDraw = 2; /* valid until this engine moves twice */
9674         if (gameMode == TwoMachinesPlay) {
9675             if (cps->other->offeredDraw) {
9676                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9677             /* [HGM] in two-machine mode we delay relaying draw offer      */
9678             /* until after we also have move, to see if it is really claim */
9679             }
9680         } else if (gameMode == MachinePlaysWhite ||
9681                    gameMode == MachinePlaysBlack) {
9682           if (userOfferedDraw) {
9683             DisplayInformation(_("Machine accepts your draw offer"));
9684             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9685           } else {
9686             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9687           }
9688         }
9689     }
9690
9691
9692     /*
9693      * Look for thinking output
9694      */
9695     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9696           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9697                                 ) {
9698         int plylev, mvleft, mvtot, curscore, time;
9699         char mvname[MOVE_LEN];
9700         u64 nodes; // [DM]
9701         char plyext;
9702         int ignore = FALSE;
9703         int prefixHint = FALSE;
9704         mvname[0] = NULLCHAR;
9705
9706         switch (gameMode) {
9707           case MachinePlaysBlack:
9708           case IcsPlayingBlack:
9709             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9710             break;
9711           case MachinePlaysWhite:
9712           case IcsPlayingWhite:
9713             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9714             break;
9715           case AnalyzeMode:
9716           case AnalyzeFile:
9717             break;
9718           case IcsObserving: /* [DM] icsEngineAnalyze */
9719             if (!appData.icsEngineAnalyze) ignore = TRUE;
9720             break;
9721           case TwoMachinesPlay:
9722             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9723                 ignore = TRUE;
9724             }
9725             break;
9726           default:
9727             ignore = TRUE;
9728             break;
9729         }
9730
9731         if (!ignore) {
9732             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9733             int solved = 0;
9734             buf1[0] = NULLCHAR;
9735             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9736                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9737                 char score_buf[MSG_SIZ];
9738
9739                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9740                     nodes += u64Const(0x100000000);
9741
9742                 if (plyext != ' ' && plyext != '\t') {
9743                     time *= 100;
9744                 }
9745
9746                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9747                 if( cps->scoreIsAbsolute &&
9748                     ( gameMode == MachinePlaysBlack ||
9749                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9750                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9751                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9752                      !WhiteOnMove(currentMove)
9753                     ) )
9754                 {
9755                     curscore = -curscore;
9756                 }
9757
9758                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9759
9760                 if(*bestMove) { // rememer time best EPD move was first found
9761                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9762                     ChessMove mt; char *p = bestMove;
9763                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9764                     solved = 0;
9765                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9766                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9767                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9768                             solved = 1;
9769                             break;
9770                         }
9771                         while(*p && *p != ' ') p++;
9772                         while(*p == ' ') p++;
9773                     }
9774                     if(!solved) solvingTime = -1;
9775                 }
9776                 if(*avoidMove && !solved) {
9777                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9778                     ChessMove mt; char *p = avoidMove, solved = 1;
9779                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9780                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9781                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9782                             solved = 0; solvingTime = -2;
9783                             break;
9784                         }
9785                         while(*p && *p != ' ') p++;
9786                         while(*p == ' ') p++;
9787                     }
9788                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9789                 }
9790
9791                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9792                         char buf[MSG_SIZ];
9793                         FILE *f;
9794                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9795                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9796                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9797                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9798                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9799                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9800                                 fclose(f);
9801                         }
9802                         else
9803                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9804                           DisplayError(_("failed writing PV"), 0);
9805                 }
9806
9807                 tempStats.depth = plylev;
9808                 tempStats.nodes = nodes;
9809                 tempStats.time = time;
9810                 tempStats.score = curscore;
9811                 tempStats.got_only_move = 0;
9812
9813                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9814                         int ticklen;
9815
9816                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9817                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9818                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9819                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9820                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9821                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9822                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9823                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9824                 }
9825
9826                 /* Buffer overflow protection */
9827                 if (pv[0] != NULLCHAR) {
9828                     if (strlen(pv) >= sizeof(tempStats.movelist)
9829                         && appData.debugMode) {
9830                         fprintf(debugFP,
9831                                 "PV is too long; using the first %u bytes.\n",
9832                                 (unsigned) sizeof(tempStats.movelist) - 1);
9833                     }
9834
9835                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9836                 } else {
9837                     sprintf(tempStats.movelist, " no PV\n");
9838                 }
9839
9840                 if (tempStats.seen_stat) {
9841                     tempStats.ok_to_send = 1;
9842                 }
9843
9844                 if (strchr(tempStats.movelist, '(') != NULL) {
9845                     tempStats.line_is_book = 1;
9846                     tempStats.nr_moves = 0;
9847                     tempStats.moves_left = 0;
9848                 } else {
9849                     tempStats.line_is_book = 0;
9850                 }
9851
9852                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9853                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9854
9855                 SendProgramStatsToFrontend( cps, &tempStats );
9856
9857                 /*
9858                     [AS] Protect the thinkOutput buffer from overflow... this
9859                     is only useful if buf1 hasn't overflowed first!
9860                 */
9861                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9862                 if(curscore >= MATE_SCORE) 
9863                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9864                 else if(curscore <= -MATE_SCORE) 
9865                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9866                 else
9867                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9868                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9869                          plylev,
9870                          (gameMode == TwoMachinesPlay ?
9871                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9872                          score_buf,
9873                          prefixHint ? lastHint : "",
9874                          prefixHint ? " " : "" );
9875
9876                 if( buf1[0] != NULLCHAR ) {
9877                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9878
9879                     if( strlen(pv) > max_len ) {
9880                         if( appData.debugMode) {
9881                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9882                         }
9883                         pv[max_len+1] = '\0';
9884                     }
9885
9886                     strcat( thinkOutput, pv);
9887                 }
9888
9889                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9890                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9891                     DisplayMove(currentMove - 1);
9892                 }
9893                 return;
9894
9895             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9896                 /* crafty (9.25+) says "(only move) <move>"
9897                  * if there is only 1 legal move
9898                  */
9899                 sscanf(p, "(only move) %s", buf1);
9900                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9901                 sprintf(programStats.movelist, "%s (only move)", buf1);
9902                 programStats.depth = 1;
9903                 programStats.nr_moves = 1;
9904                 programStats.moves_left = 1;
9905                 programStats.nodes = 1;
9906                 programStats.time = 1;
9907                 programStats.got_only_move = 1;
9908
9909                 /* Not really, but we also use this member to
9910                    mean "line isn't going to change" (Crafty
9911                    isn't searching, so stats won't change) */
9912                 programStats.line_is_book = 1;
9913
9914                 SendProgramStatsToFrontend( cps, &programStats );
9915
9916                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9917                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9918                     DisplayMove(currentMove - 1);
9919                 }
9920                 return;
9921             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9922                               &time, &nodes, &plylev, &mvleft,
9923                               &mvtot, mvname) >= 5) {
9924                 /* The stat01: line is from Crafty (9.29+) in response
9925                    to the "." command */
9926                 programStats.seen_stat = 1;
9927                 cps->maybeThinking = TRUE;
9928
9929                 if (programStats.got_only_move || !appData.periodicUpdates)
9930                   return;
9931
9932                 programStats.depth = plylev;
9933                 programStats.time = time;
9934                 programStats.nodes = nodes;
9935                 programStats.moves_left = mvleft;
9936                 programStats.nr_moves = mvtot;
9937                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9938                 programStats.ok_to_send = 1;
9939                 programStats.movelist[0] = '\0';
9940
9941                 SendProgramStatsToFrontend( cps, &programStats );
9942
9943                 return;
9944
9945             } else if (strncmp(message,"++",2) == 0) {
9946                 /* Crafty 9.29+ outputs this */
9947                 programStats.got_fail = 2;
9948                 return;
9949
9950             } else if (strncmp(message,"--",2) == 0) {
9951                 /* Crafty 9.29+ outputs this */
9952                 programStats.got_fail = 1;
9953                 return;
9954
9955             } else if (thinkOutput[0] != NULLCHAR &&
9956                        strncmp(message, "    ", 4) == 0) {
9957                 unsigned message_len;
9958
9959                 p = message;
9960                 while (*p && *p == ' ') p++;
9961
9962                 message_len = strlen( p );
9963
9964                 /* [AS] Avoid buffer overflow */
9965                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9966                     strcat(thinkOutput, " ");
9967                     strcat(thinkOutput, p);
9968                 }
9969
9970                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9971                     strcat(programStats.movelist, " ");
9972                     strcat(programStats.movelist, p);
9973                 }
9974
9975                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9976                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9977                     DisplayMove(currentMove - 1);
9978                 }
9979                 return;
9980             }
9981         }
9982         else {
9983             buf1[0] = NULLCHAR;
9984
9985             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9986                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9987             {
9988                 ChessProgramStats cpstats;
9989
9990                 if (plyext != ' ' && plyext != '\t') {
9991                     time *= 100;
9992                 }
9993
9994                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9995                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9996                     curscore = -curscore;
9997                 }
9998
9999                 cpstats.depth = plylev;
10000                 cpstats.nodes = nodes;
10001                 cpstats.time = time;
10002                 cpstats.score = curscore;
10003                 cpstats.got_only_move = 0;
10004                 cpstats.movelist[0] = '\0';
10005
10006                 if (buf1[0] != NULLCHAR) {
10007                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10008                 }
10009
10010                 cpstats.ok_to_send = 0;
10011                 cpstats.line_is_book = 0;
10012                 cpstats.nr_moves = 0;
10013                 cpstats.moves_left = 0;
10014
10015                 SendProgramStatsToFrontend( cps, &cpstats );
10016             }
10017         }
10018     }
10019 }
10020
10021
10022 /* Parse a game score from the character string "game", and
10023    record it as the history of the current game.  The game
10024    score is NOT assumed to start from the standard position.
10025    The display is not updated in any way.
10026    */
10027 void
10028 ParseGameHistory (char *game)
10029 {
10030     ChessMove moveType;
10031     int fromX, fromY, toX, toY, boardIndex;
10032     char promoChar;
10033     char *p, *q;
10034     char buf[MSG_SIZ];
10035
10036     if (appData.debugMode)
10037       fprintf(debugFP, "Parsing game history: %s\n", game);
10038
10039     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10040     gameInfo.site = StrSave(appData.icsHost);
10041     gameInfo.date = PGNDate();
10042     gameInfo.round = StrSave("-");
10043
10044     /* Parse out names of players */
10045     while (*game == ' ') game++;
10046     p = buf;
10047     while (*game != ' ') *p++ = *game++;
10048     *p = NULLCHAR;
10049     gameInfo.white = StrSave(buf);
10050     while (*game == ' ') game++;
10051     p = buf;
10052     while (*game != ' ' && *game != '\n') *p++ = *game++;
10053     *p = NULLCHAR;
10054     gameInfo.black = StrSave(buf);
10055
10056     /* Parse moves */
10057     boardIndex = blackPlaysFirst ? 1 : 0;
10058     yynewstr(game);
10059     for (;;) {
10060         yyboardindex = boardIndex;
10061         moveType = (ChessMove) Myylex();
10062         switch (moveType) {
10063           case IllegalMove:             /* maybe suicide chess, etc. */
10064   if (appData.debugMode) {
10065     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10066     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10067     setbuf(debugFP, NULL);
10068   }
10069           case WhitePromotion:
10070           case BlackPromotion:
10071           case WhiteNonPromotion:
10072           case BlackNonPromotion:
10073           case NormalMove:
10074           case FirstLeg:
10075           case WhiteCapturesEnPassant:
10076           case BlackCapturesEnPassant:
10077           case WhiteKingSideCastle:
10078           case WhiteQueenSideCastle:
10079           case BlackKingSideCastle:
10080           case BlackQueenSideCastle:
10081           case WhiteKingSideCastleWild:
10082           case WhiteQueenSideCastleWild:
10083           case BlackKingSideCastleWild:
10084           case BlackQueenSideCastleWild:
10085           /* PUSH Fabien */
10086           case WhiteHSideCastleFR:
10087           case WhiteASideCastleFR:
10088           case BlackHSideCastleFR:
10089           case BlackASideCastleFR:
10090           /* POP Fabien */
10091             fromX = currentMoveString[0] - AAA;
10092             fromY = currentMoveString[1] - ONE;
10093             toX = currentMoveString[2] - AAA;
10094             toY = currentMoveString[3] - ONE;
10095             promoChar = currentMoveString[4];
10096             break;
10097           case WhiteDrop:
10098           case BlackDrop:
10099             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10100             fromX = moveType == WhiteDrop ?
10101               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10102             (int) CharToPiece(ToLower(currentMoveString[0]));
10103             fromY = DROP_RANK;
10104             toX = currentMoveString[2] - AAA;
10105             toY = currentMoveString[3] - ONE;
10106             promoChar = NULLCHAR;
10107             break;
10108           case AmbiguousMove:
10109             /* bug? */
10110             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10111   if (appData.debugMode) {
10112     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10113     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10114     setbuf(debugFP, NULL);
10115   }
10116             DisplayError(buf, 0);
10117             return;
10118           case ImpossibleMove:
10119             /* bug? */
10120             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10121   if (appData.debugMode) {
10122     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10123     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10124     setbuf(debugFP, NULL);
10125   }
10126             DisplayError(buf, 0);
10127             return;
10128           case EndOfFile:
10129             if (boardIndex < backwardMostMove) {
10130                 /* Oops, gap.  How did that happen? */
10131                 DisplayError(_("Gap in move list"), 0);
10132                 return;
10133             }
10134             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10135             if (boardIndex > forwardMostMove) {
10136                 forwardMostMove = boardIndex;
10137             }
10138             return;
10139           case ElapsedTime:
10140             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10141                 strcat(parseList[boardIndex-1], " ");
10142                 strcat(parseList[boardIndex-1], yy_text);
10143             }
10144             continue;
10145           case Comment:
10146           case PGNTag:
10147           case NAG:
10148           default:
10149             /* ignore */
10150             continue;
10151           case WhiteWins:
10152           case BlackWins:
10153           case GameIsDrawn:
10154           case GameUnfinished:
10155             if (gameMode == IcsExamining) {
10156                 if (boardIndex < backwardMostMove) {
10157                     /* Oops, gap.  How did that happen? */
10158                     return;
10159                 }
10160                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10161                 return;
10162             }
10163             gameInfo.result = moveType;
10164             p = strchr(yy_text, '{');
10165             if (p == NULL) p = strchr(yy_text, '(');
10166             if (p == NULL) {
10167                 p = yy_text;
10168                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10169             } else {
10170                 q = strchr(p, *p == '{' ? '}' : ')');
10171                 if (q != NULL) *q = NULLCHAR;
10172                 p++;
10173             }
10174             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10175             gameInfo.resultDetails = StrSave(p);
10176             continue;
10177         }
10178         if (boardIndex >= forwardMostMove &&
10179             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10180             backwardMostMove = blackPlaysFirst ? 1 : 0;
10181             return;
10182         }
10183         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10184                                  fromY, fromX, toY, toX, promoChar,
10185                                  parseList[boardIndex]);
10186         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10187         /* currentMoveString is set as a side-effect of yylex */
10188         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10189         strcat(moveList[boardIndex], "\n");
10190         boardIndex++;
10191         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10192         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10193           case MT_NONE:
10194           case MT_STALEMATE:
10195           default:
10196             break;
10197           case MT_CHECK:
10198             if(!IS_SHOGI(gameInfo.variant))
10199                 strcat(parseList[boardIndex - 1], "+");
10200             break;
10201           case MT_CHECKMATE:
10202           case MT_STAINMATE:
10203             strcat(parseList[boardIndex - 1], "#");
10204             break;
10205         }
10206     }
10207 }
10208
10209
10210 /* Apply a move to the given board  */
10211 void
10212 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10213 {
10214   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10215   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10216
10217     /* [HGM] compute & store e.p. status and castling rights for new position */
10218     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10219
10220       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10221       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10222       board[EP_STATUS] = EP_NONE;
10223       board[EP_FILE] = board[EP_RANK] = 100;
10224
10225   if (fromY == DROP_RANK) {
10226         /* must be first */
10227         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10228             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10229             return;
10230         }
10231         piece = board[toY][toX] = (ChessSquare) fromX;
10232   } else {
10233 //      ChessSquare victim;
10234       int i;
10235
10236       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10237 //           victim = board[killY][killX],
10238            killed = board[killY][killX],
10239            board[killY][killX] = EmptySquare,
10240            board[EP_STATUS] = EP_CAPTURE;
10241            if( kill2X >= 0 && kill2Y >= 0)
10242              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10243       }
10244
10245       if( board[toY][toX] != EmptySquare ) {
10246            board[EP_STATUS] = EP_CAPTURE;
10247            if( (fromX != toX || fromY != toY) && // not igui!
10248                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10249                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10250                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10251            }
10252       }
10253
10254       pawn = board[fromY][fromX];
10255       if( pawn == WhiteLance || pawn == BlackLance ) {
10256            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10257                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10258                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10259            }
10260       }
10261       if( pawn == WhitePawn ) {
10262            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10263                board[EP_STATUS] = EP_PAWN_MOVE;
10264            if( toY-fromY>=2) {
10265                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10266                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10267                         gameInfo.variant != VariantBerolina || toX < fromX)
10268                       board[EP_STATUS] = toX | berolina;
10269                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10270                         gameInfo.variant != VariantBerolina || toX > fromX)
10271                       board[EP_STATUS] = toX;
10272            }
10273       } else
10274       if( pawn == BlackPawn ) {
10275            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10276                board[EP_STATUS] = EP_PAWN_MOVE;
10277            if( toY-fromY<= -2) {
10278                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10279                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10280                         gameInfo.variant != VariantBerolina || toX < fromX)
10281                       board[EP_STATUS] = toX | berolina;
10282                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10283                         gameInfo.variant != VariantBerolina || toX > fromX)
10284                       board[EP_STATUS] = toX;
10285            }
10286        }
10287
10288        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10289        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10290        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10291        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10292
10293        for(i=0; i<nrCastlingRights; i++) {
10294            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10295               board[CASTLING][i] == toX   && castlingRank[i] == toY
10296              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10297        }
10298
10299        if(gameInfo.variant == VariantSChess) { // update virginity
10300            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10301            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10302            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10303            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10304        }
10305
10306      if (fromX == toX && fromY == toY && killX < 0) return;
10307
10308      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10309      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10310      if(gameInfo.variant == VariantKnightmate)
10311          king += (int) WhiteUnicorn - (int) WhiteKing;
10312
10313     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10314        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10315         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10316         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10317         board[EP_STATUS] = EP_NONE; // capture was fake!
10318     } else
10319     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10320         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10321         board[toY][toX] = piece;
10322         board[EP_STATUS] = EP_NONE; // capture was fake!
10323     } else
10324     /* Code added by Tord: */
10325     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10326     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10327         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10328       board[EP_STATUS] = EP_NONE; // capture was fake!
10329       board[fromY][fromX] = EmptySquare;
10330       board[toY][toX] = EmptySquare;
10331       if((toX > fromX) != (piece == WhiteRook)) {
10332         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10333       } else {
10334         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10335       }
10336     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10337                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10338       board[EP_STATUS] = EP_NONE;
10339       board[fromY][fromX] = EmptySquare;
10340       board[toY][toX] = EmptySquare;
10341       if((toX > fromX) != (piece == BlackRook)) {
10342         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10343       } else {
10344         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10345       }
10346     /* End of code added by Tord */
10347
10348     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10349         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10350         board[toY][toX] = piece;
10351     } else if (board[fromY][fromX] == king
10352         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10353         && toY == fromY && toX > fromX+1) {
10354         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10355                                                                                              ; // castle with nearest piece
10356         board[fromY][toX-1] = board[fromY][rookX];
10357         board[fromY][rookX] = EmptySquare;
10358         board[fromY][fromX] = EmptySquare;
10359         board[toY][toX] = king;
10360     } else if (board[fromY][fromX] == king
10361         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10362                && toY == fromY && toX < fromX-1) {
10363         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10364                                                                                   ; // castle with nearest piece
10365         board[fromY][toX+1] = board[fromY][rookX];
10366         board[fromY][rookX] = EmptySquare;
10367         board[fromY][fromX] = EmptySquare;
10368         board[toY][toX] = king;
10369     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10370                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10371                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10372                ) {
10373         /* white pawn promotion */
10374         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10375         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10376             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10377         board[fromY][fromX] = EmptySquare;
10378     } else if ((fromY >= BOARD_HEIGHT>>1)
10379                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10380                && (toX != fromX)
10381                && gameInfo.variant != VariantXiangqi
10382                && gameInfo.variant != VariantBerolina
10383                && (pawn == WhitePawn)
10384                && (board[toY][toX] == EmptySquare)) {
10385         board[fromY][fromX] = EmptySquare;
10386         board[toY][toX] = piece;
10387         if(toY == epRank - 128 + 1)
10388             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10389         else
10390             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10391     } else if ((fromY == BOARD_HEIGHT-4)
10392                && (toX == fromX)
10393                && gameInfo.variant == VariantBerolina
10394                && (board[fromY][fromX] == WhitePawn)
10395                && (board[toY][toX] == EmptySquare)) {
10396         board[fromY][fromX] = EmptySquare;
10397         board[toY][toX] = WhitePawn;
10398         if(oldEP & EP_BEROLIN_A) {
10399                 captured = board[fromY][fromX-1];
10400                 board[fromY][fromX-1] = EmptySquare;
10401         }else{  captured = board[fromY][fromX+1];
10402                 board[fromY][fromX+1] = EmptySquare;
10403         }
10404     } else if (board[fromY][fromX] == king
10405         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10406                && toY == fromY && toX > fromX+1) {
10407         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10408                                                                                              ;
10409         board[fromY][toX-1] = board[fromY][rookX];
10410         board[fromY][rookX] = EmptySquare;
10411         board[fromY][fromX] = EmptySquare;
10412         board[toY][toX] = king;
10413     } else if (board[fromY][fromX] == king
10414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10415                && toY == fromY && toX < fromX-1) {
10416         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10417                                                                                 ;
10418         board[fromY][toX+1] = board[fromY][rookX];
10419         board[fromY][rookX] = EmptySquare;
10420         board[fromY][fromX] = EmptySquare;
10421         board[toY][toX] = king;
10422     } else if (fromY == 7 && fromX == 3
10423                && board[fromY][fromX] == BlackKing
10424                && toY == 7 && toX == 5) {
10425         board[fromY][fromX] = EmptySquare;
10426         board[toY][toX] = BlackKing;
10427         board[fromY][7] = EmptySquare;
10428         board[toY][4] = BlackRook;
10429     } else if (fromY == 7 && fromX == 3
10430                && board[fromY][fromX] == BlackKing
10431                && toY == 7 && toX == 1) {
10432         board[fromY][fromX] = EmptySquare;
10433         board[toY][toX] = BlackKing;
10434         board[fromY][0] = EmptySquare;
10435         board[toY][2] = BlackRook;
10436     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10437                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10438                && toY < promoRank && promoChar
10439                ) {
10440         /* black pawn promotion */
10441         board[toY][toX] = CharToPiece(ToLower(promoChar));
10442         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10443             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10444         board[fromY][fromX] = EmptySquare;
10445     } else if ((fromY < BOARD_HEIGHT>>1)
10446                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10447                && (toX != fromX)
10448                && gameInfo.variant != VariantXiangqi
10449                && gameInfo.variant != VariantBerolina
10450                && (pawn == BlackPawn)
10451                && (board[toY][toX] == EmptySquare)) {
10452         board[fromY][fromX] = EmptySquare;
10453         board[toY][toX] = piece;
10454         if(toY == epRank - 128 - 1)
10455             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10456         else
10457             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10458     } else if ((fromY == 3)
10459                && (toX == fromX)
10460                && gameInfo.variant == VariantBerolina
10461                && (board[fromY][fromX] == BlackPawn)
10462                && (board[toY][toX] == EmptySquare)) {
10463         board[fromY][fromX] = EmptySquare;
10464         board[toY][toX] = BlackPawn;
10465         if(oldEP & EP_BEROLIN_A) {
10466                 captured = board[fromY][fromX-1];
10467                 board[fromY][fromX-1] = EmptySquare;
10468         }else{  captured = board[fromY][fromX+1];
10469                 board[fromY][fromX+1] = EmptySquare;
10470         }
10471     } else {
10472         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10473         board[fromY][fromX] = EmptySquare;
10474         board[toY][toX] = piece;
10475     }
10476   }
10477
10478     if (gameInfo.holdingsWidth != 0) {
10479
10480       /* !!A lot more code needs to be written to support holdings  */
10481       /* [HGM] OK, so I have written it. Holdings are stored in the */
10482       /* penultimate board files, so they are automaticlly stored   */
10483       /* in the game history.                                       */
10484       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10485                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10486         /* Delete from holdings, by decreasing count */
10487         /* and erasing image if necessary            */
10488         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10489         if(p < (int) BlackPawn) { /* white drop */
10490              p -= (int)WhitePawn;
10491                  p = PieceToNumber((ChessSquare)p);
10492              if(p >= gameInfo.holdingsSize) p = 0;
10493              if(--board[p][BOARD_WIDTH-2] <= 0)
10494                   board[p][BOARD_WIDTH-1] = EmptySquare;
10495              if((int)board[p][BOARD_WIDTH-2] < 0)
10496                         board[p][BOARD_WIDTH-2] = 0;
10497         } else {                  /* black drop */
10498              p -= (int)BlackPawn;
10499                  p = PieceToNumber((ChessSquare)p);
10500              if(p >= gameInfo.holdingsSize) p = 0;
10501              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10502                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10503              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10504                         board[BOARD_HEIGHT-1-p][1] = 0;
10505         }
10506       }
10507       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10508           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10509         /* [HGM] holdings: Add to holdings, if holdings exist */
10510         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10511                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10512                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10513         }
10514         p = (int) captured;
10515         if (p >= (int) BlackPawn) {
10516           p -= (int)BlackPawn;
10517           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10518                   /* Restore shogi-promoted piece to its original  first */
10519                   captured = (ChessSquare) (DEMOTED(captured));
10520                   p = DEMOTED(p);
10521           }
10522           p = PieceToNumber((ChessSquare)p);
10523           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10524           board[p][BOARD_WIDTH-2]++;
10525           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10526         } else {
10527           p -= (int)WhitePawn;
10528           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10529                   captured = (ChessSquare) (DEMOTED(captured));
10530                   p = DEMOTED(p);
10531           }
10532           p = PieceToNumber((ChessSquare)p);
10533           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10534           board[BOARD_HEIGHT-1-p][1]++;
10535           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10536         }
10537       }
10538     } else if (gameInfo.variant == VariantAtomic) {
10539       if (captured != EmptySquare) {
10540         int y, x;
10541         for (y = toY-1; y <= toY+1; y++) {
10542           for (x = toX-1; x <= toX+1; x++) {
10543             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10544                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10545               board[y][x] = EmptySquare;
10546             }
10547           }
10548         }
10549         board[toY][toX] = EmptySquare;
10550       }
10551     }
10552
10553     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10554         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10555     } else
10556     if(promoChar == '+') {
10557         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10558         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10559         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10560           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10561     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10562         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10563         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10564            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10565         board[toY][toX] = newPiece;
10566     }
10567     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10568                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10569         // [HGM] superchess: take promotion piece out of holdings
10570         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10571         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10572             if(!--board[k][BOARD_WIDTH-2])
10573                 board[k][BOARD_WIDTH-1] = EmptySquare;
10574         } else {
10575             if(!--board[BOARD_HEIGHT-1-k][1])
10576                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10577         }
10578     }
10579 }
10580
10581 /* Updates forwardMostMove */
10582 void
10583 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10584 {
10585     int x = toX, y = toY;
10586     char *s = parseList[forwardMostMove];
10587     ChessSquare p = boards[forwardMostMove][toY][toX];
10588 //    forwardMostMove++; // [HGM] bare: moved downstream
10589
10590     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10591     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10592     (void) CoordsToAlgebraic(boards[forwardMostMove],
10593                              PosFlags(forwardMostMove),
10594                              fromY, fromX, y, x, (killX < 0)*promoChar,
10595                              s);
10596     if(kill2X >= 0 && kill2Y >= 0)
10597         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10598     if(killX >= 0 && killY >= 0)
10599         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10600                                            toX + AAA, toY + ONE - '0', promoChar);
10601
10602     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10603         int timeLeft; static int lastLoadFlag=0; int king, piece;
10604         piece = boards[forwardMostMove][fromY][fromX];
10605         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10606         if(gameInfo.variant == VariantKnightmate)
10607             king += (int) WhiteUnicorn - (int) WhiteKing;
10608         if(forwardMostMove == 0) {
10609             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10610                 fprintf(serverMoves, "%s;", UserName());
10611             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10612                 fprintf(serverMoves, "%s;", second.tidy);
10613             fprintf(serverMoves, "%s;", first.tidy);
10614             if(gameMode == MachinePlaysWhite)
10615                 fprintf(serverMoves, "%s;", UserName());
10616             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10617                 fprintf(serverMoves, "%s;", second.tidy);
10618         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10619         lastLoadFlag = loadFlag;
10620         // print base move
10621         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10622         // print castling suffix
10623         if( toY == fromY && piece == king ) {
10624             if(toX-fromX > 1)
10625                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10626             if(fromX-toX >1)
10627                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10628         }
10629         // e.p. suffix
10630         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10631              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10632              boards[forwardMostMove][toY][toX] == EmptySquare
10633              && fromX != toX && fromY != toY)
10634                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10635         // promotion suffix
10636         if(promoChar != NULLCHAR) {
10637             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10638                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10639                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10640             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10641         }
10642         if(!loadFlag) {
10643                 char buf[MOVE_LEN*2], *p; int len;
10644             fprintf(serverMoves, "/%d/%d",
10645                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10646             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10647             else                      timeLeft = blackTimeRemaining/1000;
10648             fprintf(serverMoves, "/%d", timeLeft);
10649                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10650                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10651                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10652                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10653             fprintf(serverMoves, "/%s", buf);
10654         }
10655         fflush(serverMoves);
10656     }
10657
10658     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10659         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10660       return;
10661     }
10662     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10663     if (commentList[forwardMostMove+1] != NULL) {
10664         free(commentList[forwardMostMove+1]);
10665         commentList[forwardMostMove+1] = NULL;
10666     }
10667     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10668     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10669     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10670     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10671     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10672     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10673     adjustedClock = FALSE;
10674     gameInfo.result = GameUnfinished;
10675     if (gameInfo.resultDetails != NULL) {
10676         free(gameInfo.resultDetails);
10677         gameInfo.resultDetails = NULL;
10678     }
10679     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10680                               moveList[forwardMostMove - 1]);
10681     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10682       case MT_NONE:
10683       case MT_STALEMATE:
10684       default:
10685         break;
10686       case MT_CHECK:
10687         if(!IS_SHOGI(gameInfo.variant))
10688             strcat(parseList[forwardMostMove - 1], "+");
10689         break;
10690       case MT_CHECKMATE:
10691       case MT_STAINMATE:
10692         strcat(parseList[forwardMostMove - 1], "#");
10693         break;
10694     }
10695 }
10696
10697 /* Updates currentMove if not pausing */
10698 void
10699 ShowMove (int fromX, int fromY, int toX, int toY)
10700 {
10701     int instant = (gameMode == PlayFromGameFile) ?
10702         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10703     if(appData.noGUI) return;
10704     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10705         if (!instant) {
10706             if (forwardMostMove == currentMove + 1) {
10707                 AnimateMove(boards[forwardMostMove - 1],
10708                             fromX, fromY, toX, toY);
10709             }
10710         }
10711         currentMove = forwardMostMove;
10712     }
10713
10714     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10715
10716     if (instant) return;
10717
10718     DisplayMove(currentMove - 1);
10719     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10720             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10721                 SetHighlights(fromX, fromY, toX, toY);
10722             }
10723     }
10724     DrawPosition(FALSE, boards[currentMove]);
10725     DisplayBothClocks();
10726     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10727 }
10728
10729 void
10730 SendEgtPath (ChessProgramState *cps)
10731 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10732         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10733
10734         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10735
10736         while(*p) {
10737             char c, *q = name+1, *r, *s;
10738
10739             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10740             while(*p && *p != ',') *q++ = *p++;
10741             *q++ = ':'; *q = 0;
10742             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10743                 strcmp(name, ",nalimov:") == 0 ) {
10744                 // take nalimov path from the menu-changeable option first, if it is defined
10745               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10746                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10747             } else
10748             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10749                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10750                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10751                 s = r = StrStr(s, ":") + 1; // beginning of path info
10752                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10753                 c = *r; *r = 0;             // temporarily null-terminate path info
10754                     *--q = 0;               // strip of trailig ':' from name
10755                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10756                 *r = c;
10757                 SendToProgram(buf,cps);     // send egtbpath command for this format
10758             }
10759             if(*p == ',') p++; // read away comma to position for next format name
10760         }
10761 }
10762
10763 static int
10764 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10765 {
10766       int width = 8, height = 8, holdings = 0;             // most common sizes
10767       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10768       // correct the deviations default for each variant
10769       if( v == VariantXiangqi ) width = 9,  height = 10;
10770       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10771       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10772       if( v == VariantCapablanca || v == VariantCapaRandom ||
10773           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10774                                 width = 10;
10775       if( v == VariantCourier ) width = 12;
10776       if( v == VariantSuper )                            holdings = 8;
10777       if( v == VariantGreat )   width = 10,              holdings = 8;
10778       if( v == VariantSChess )                           holdings = 7;
10779       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10780       if( v == VariantChuChess) width = 10, height = 10;
10781       if( v == VariantChu )     width = 12, height = 12;
10782       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10783              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10784              holdingsSize >= 0 && holdingsSize != holdings;
10785 }
10786
10787 char variantError[MSG_SIZ];
10788
10789 char *
10790 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10791 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10792       char *p, *variant = VariantName(v);
10793       static char b[MSG_SIZ];
10794       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10795            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10796                                                holdingsSize, variant); // cook up sized variant name
10797            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10798            if(StrStr(list, b) == NULL) {
10799                // specific sized variant not known, check if general sizing allowed
10800                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10801                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10802                             boardWidth, boardHeight, holdingsSize, engine);
10803                    return NULL;
10804                }
10805                /* [HGM] here we really should compare with the maximum supported board size */
10806            }
10807       } else snprintf(b, MSG_SIZ,"%s", variant);
10808       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10809       p = StrStr(list, b);
10810       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10811       if(p == NULL) {
10812           // occurs not at all in list, or only as sub-string
10813           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10814           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10815               int l = strlen(variantError);
10816               char *q;
10817               while(p != list && p[-1] != ',') p--;
10818               q = strchr(p, ',');
10819               if(q) *q = NULLCHAR;
10820               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10821               if(q) *q= ',';
10822           }
10823           return NULL;
10824       }
10825       return b;
10826 }
10827
10828 void
10829 InitChessProgram (ChessProgramState *cps, int setup)
10830 /* setup needed to setup FRC opening position */
10831 {
10832     char buf[MSG_SIZ], *b;
10833     if (appData.noChessProgram) return;
10834     hintRequested = FALSE;
10835     bookRequested = FALSE;
10836
10837     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10838     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10839     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10840     if(cps->memSize) { /* [HGM] memory */
10841       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10842         SendToProgram(buf, cps);
10843     }
10844     SendEgtPath(cps); /* [HGM] EGT */
10845     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10846       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10847         SendToProgram(buf, cps);
10848     }
10849
10850     setboardSpoiledMachineBlack = FALSE;
10851     SendToProgram(cps->initString, cps);
10852     if (gameInfo.variant != VariantNormal &&
10853         gameInfo.variant != VariantLoadable
10854         /* [HGM] also send variant if board size non-standard */
10855         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10856
10857       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10858                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10859
10860       if (b == NULL) {
10861         VariantClass v;
10862         char c, *q = cps->variants, *p = strchr(q, ',');
10863         if(p) *p = NULLCHAR;
10864         v = StringToVariant(q);
10865         DisplayError(variantError, 0);
10866         if(v != VariantUnknown && cps == &first) {
10867             int w, h, s;
10868             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10869                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10870             ASSIGN(appData.variant, q);
10871             Reset(TRUE, FALSE);
10872         }
10873         if(p) *p = ',';
10874         return;
10875       }
10876
10877       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10878       SendToProgram(buf, cps);
10879     }
10880     currentlyInitializedVariant = gameInfo.variant;
10881
10882     /* [HGM] send opening position in FRC to first engine */
10883     if(setup) {
10884           SendToProgram("force\n", cps);
10885           SendBoard(cps, 0);
10886           /* engine is now in force mode! Set flag to wake it up after first move. */
10887           setboardSpoiledMachineBlack = 1;
10888     }
10889
10890     if (cps->sendICS) {
10891       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10892       SendToProgram(buf, cps);
10893     }
10894     cps->maybeThinking = FALSE;
10895     cps->offeredDraw = 0;
10896     if (!appData.icsActive) {
10897         SendTimeControl(cps, movesPerSession, timeControl,
10898                         timeIncrement, appData.searchDepth,
10899                         searchTime);
10900     }
10901     if (appData.showThinking
10902         // [HGM] thinking: four options require thinking output to be sent
10903         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10904                                 ) {
10905         SendToProgram("post\n", cps);
10906     }
10907     SendToProgram("hard\n", cps);
10908     if (!appData.ponderNextMove) {
10909         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10910            it without being sure what state we are in first.  "hard"
10911            is not a toggle, so that one is OK.
10912          */
10913         SendToProgram("easy\n", cps);
10914     }
10915     if (cps->usePing) {
10916       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10917       SendToProgram(buf, cps);
10918     }
10919     cps->initDone = TRUE;
10920     ClearEngineOutputPane(cps == &second);
10921 }
10922
10923
10924 void
10925 ResendOptions (ChessProgramState *cps)
10926 { // send the stored value of the options
10927   int i;
10928   char buf[MSG_SIZ];
10929   Option *opt = cps->option;
10930   for(i=0; i<cps->nrOptions; i++, opt++) {
10931       switch(opt->type) {
10932         case Spin:
10933         case Slider:
10934         case CheckBox:
10935             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10936           break;
10937         case ComboBox:
10938           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10939           break;
10940         default:
10941             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10942           break;
10943         case Button:
10944         case SaveButton:
10945           continue;
10946       }
10947       SendToProgram(buf, cps);
10948   }
10949 }
10950
10951 void
10952 StartChessProgram (ChessProgramState *cps)
10953 {
10954     char buf[MSG_SIZ];
10955     int err;
10956
10957     if (appData.noChessProgram) return;
10958     cps->initDone = FALSE;
10959
10960     if (strcmp(cps->host, "localhost") == 0) {
10961         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10962     } else if (*appData.remoteShell == NULLCHAR) {
10963         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10964     } else {
10965         if (*appData.remoteUser == NULLCHAR) {
10966           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10967                     cps->program);
10968         } else {
10969           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10970                     cps->host, appData.remoteUser, cps->program);
10971         }
10972         err = StartChildProcess(buf, "", &cps->pr);
10973     }
10974
10975     if (err != 0) {
10976       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10977         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10978         if(cps != &first) return;
10979         appData.noChessProgram = TRUE;
10980         ThawUI();
10981         SetNCPMode();
10982 //      DisplayFatalError(buf, err, 1);
10983 //      cps->pr = NoProc;
10984 //      cps->isr = NULL;
10985         return;
10986     }
10987
10988     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10989     if (cps->protocolVersion > 1) {
10990       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10991       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10992         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10993         cps->comboCnt = 0;  //                and values of combo boxes
10994       }
10995       SendToProgram(buf, cps);
10996       if(cps->reload) ResendOptions(cps);
10997     } else {
10998       SendToProgram("xboard\n", cps);
10999     }
11000 }
11001
11002 void
11003 TwoMachinesEventIfReady P((void))
11004 {
11005   static int curMess = 0;
11006   if (first.lastPing != first.lastPong) {
11007     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11008     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11009     return;
11010   }
11011   if (second.lastPing != second.lastPong) {
11012     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11013     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11014     return;
11015   }
11016   DisplayMessage("", ""); curMess = 0;
11017   TwoMachinesEvent();
11018 }
11019
11020 char *
11021 MakeName (char *template)
11022 {
11023     time_t clock;
11024     struct tm *tm;
11025     static char buf[MSG_SIZ];
11026     char *p = buf;
11027     int i;
11028
11029     clock = time((time_t *)NULL);
11030     tm = localtime(&clock);
11031
11032     while(*p++ = *template++) if(p[-1] == '%') {
11033         switch(*template++) {
11034           case 0:   *p = 0; return buf;
11035           case 'Y': i = tm->tm_year+1900; break;
11036           case 'y': i = tm->tm_year-100; break;
11037           case 'M': i = tm->tm_mon+1; break;
11038           case 'd': i = tm->tm_mday; break;
11039           case 'h': i = tm->tm_hour; break;
11040           case 'm': i = tm->tm_min; break;
11041           case 's': i = tm->tm_sec; break;
11042           default:  i = 0;
11043         }
11044         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11045     }
11046     return buf;
11047 }
11048
11049 int
11050 CountPlayers (char *p)
11051 {
11052     int n = 0;
11053     while(p = strchr(p, '\n')) p++, n++; // count participants
11054     return n;
11055 }
11056
11057 FILE *
11058 WriteTourneyFile (char *results, FILE *f)
11059 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11060     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11061     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11062         // create a file with tournament description
11063         fprintf(f, "-participants {%s}\n", appData.participants);
11064         fprintf(f, "-seedBase %d\n", appData.seedBase);
11065         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11066         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11067         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11068         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11069         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11070         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11071         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11072         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11073         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11074         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11075         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11076         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11077         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11078         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11079         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11080         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11081         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11082         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11083         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11084         fprintf(f, "-smpCores %d\n", appData.smpCores);
11085         if(searchTime > 0)
11086                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11087         else {
11088                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11089                 fprintf(f, "-tc %s\n", appData.timeControl);
11090                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11091         }
11092         fprintf(f, "-results \"%s\"\n", results);
11093     }
11094     return f;
11095 }
11096
11097 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11098
11099 void
11100 Substitute (char *participants, int expunge)
11101 {
11102     int i, changed, changes=0, nPlayers=0;
11103     char *p, *q, *r, buf[MSG_SIZ];
11104     if(participants == NULL) return;
11105     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11106     r = p = participants; q = appData.participants;
11107     while(*p && *p == *q) {
11108         if(*p == '\n') r = p+1, nPlayers++;
11109         p++; q++;
11110     }
11111     if(*p) { // difference
11112         while(*p && *p++ != '\n')
11113                                  ;
11114         while(*q && *q++ != '\n')
11115                                  ;
11116       changed = nPlayers;
11117         changes = 1 + (strcmp(p, q) != 0);
11118     }
11119     if(changes == 1) { // a single engine mnemonic was changed
11120         q = r; while(*q) nPlayers += (*q++ == '\n');
11121         p = buf; while(*r && (*p = *r++) != '\n') p++;
11122         *p = NULLCHAR;
11123         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11124         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11125         if(mnemonic[i]) { // The substitute is valid
11126             FILE *f;
11127             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11128                 flock(fileno(f), LOCK_EX);
11129                 ParseArgsFromFile(f);
11130                 fseek(f, 0, SEEK_SET);
11131                 FREE(appData.participants); appData.participants = participants;
11132                 if(expunge) { // erase results of replaced engine
11133                     int len = strlen(appData.results), w, b, dummy;
11134                     for(i=0; i<len; i++) {
11135                         Pairing(i, nPlayers, &w, &b, &dummy);
11136                         if((w == changed || b == changed) && appData.results[i] == '*') {
11137                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11138                             fclose(f);
11139                             return;
11140                         }
11141                     }
11142                     for(i=0; i<len; i++) {
11143                         Pairing(i, nPlayers, &w, &b, &dummy);
11144                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11145                     }
11146                 }
11147                 WriteTourneyFile(appData.results, f);
11148                 fclose(f); // release lock
11149                 return;
11150             }
11151         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11152     }
11153     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11154     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11155     free(participants);
11156     return;
11157 }
11158
11159 int
11160 CheckPlayers (char *participants)
11161 {
11162         int i;
11163         char buf[MSG_SIZ], *p;
11164         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11165         while(p = strchr(participants, '\n')) {
11166             *p = NULLCHAR;
11167             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11168             if(!mnemonic[i]) {
11169                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11170                 *p = '\n';
11171                 DisplayError(buf, 0);
11172                 return 1;
11173             }
11174             *p = '\n';
11175             participants = p + 1;
11176         }
11177         return 0;
11178 }
11179
11180 int
11181 CreateTourney (char *name)
11182 {
11183         FILE *f;
11184         if(matchMode && strcmp(name, appData.tourneyFile)) {
11185              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11186         }
11187         if(name[0] == NULLCHAR) {
11188             if(appData.participants[0])
11189                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11190             return 0;
11191         }
11192         f = fopen(name, "r");
11193         if(f) { // file exists
11194             ASSIGN(appData.tourneyFile, name);
11195             ParseArgsFromFile(f); // parse it
11196         } else {
11197             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11198             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11199                 DisplayError(_("Not enough participants"), 0);
11200                 return 0;
11201             }
11202             if(CheckPlayers(appData.participants)) return 0;
11203             ASSIGN(appData.tourneyFile, name);
11204             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11205             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11206         }
11207         fclose(f);
11208         appData.noChessProgram = FALSE;
11209         appData.clockMode = TRUE;
11210         SetGNUMode();
11211         return 1;
11212 }
11213
11214 int
11215 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11216 {
11217     char buf[MSG_SIZ], *p, *q;
11218     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11219     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11220     skip = !all && group[0]; // if group requested, we start in skip mode
11221     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11222         p = names; q = buf; header = 0;
11223         while(*p && *p != '\n') *q++ = *p++;
11224         *q = 0;
11225         if(*p == '\n') p++;
11226         if(buf[0] == '#') {
11227             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11228             depth++; // we must be entering a new group
11229             if(all) continue; // suppress printing group headers when complete list requested
11230             header = 1;
11231             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11232         }
11233         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11234         if(engineList[i]) free(engineList[i]);
11235         engineList[i] = strdup(buf);
11236         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11237         if(engineMnemonic[i]) free(engineMnemonic[i]);
11238         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11239             strcat(buf, " (");
11240             sscanf(q + 8, "%s", buf + strlen(buf));
11241             strcat(buf, ")");
11242         }
11243         engineMnemonic[i] = strdup(buf);
11244         i++;
11245     }
11246     engineList[i] = engineMnemonic[i] = NULL;
11247     return i;
11248 }
11249
11250 // following implemented as macro to avoid type limitations
11251 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11252
11253 void
11254 SwapEngines (int n)
11255 {   // swap settings for first engine and other engine (so far only some selected options)
11256     int h;
11257     char *p;
11258     if(n == 0) return;
11259     SWAP(directory, p)
11260     SWAP(chessProgram, p)
11261     SWAP(isUCI, h)
11262     SWAP(hasOwnBookUCI, h)
11263     SWAP(protocolVersion, h)
11264     SWAP(reuse, h)
11265     SWAP(scoreIsAbsolute, h)
11266     SWAP(timeOdds, h)
11267     SWAP(logo, p)
11268     SWAP(pgnName, p)
11269     SWAP(pvSAN, h)
11270     SWAP(engOptions, p)
11271     SWAP(engInitString, p)
11272     SWAP(computerString, p)
11273     SWAP(features, p)
11274     SWAP(fenOverride, p)
11275     SWAP(NPS, h)
11276     SWAP(accumulateTC, h)
11277     SWAP(drawDepth, h)
11278     SWAP(host, p)
11279     SWAP(pseudo, h)
11280 }
11281
11282 int
11283 GetEngineLine (char *s, int n)
11284 {
11285     int i;
11286     char buf[MSG_SIZ];
11287     extern char *icsNames;
11288     if(!s || !*s) return 0;
11289     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11290     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11291     if(!mnemonic[i]) return 0;
11292     if(n == 11) return 1; // just testing if there was a match
11293     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11294     if(n == 1) SwapEngines(n);
11295     ParseArgsFromString(buf);
11296     if(n == 1) SwapEngines(n);
11297     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11298         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11299         ParseArgsFromString(buf);
11300     }
11301     return 1;
11302 }
11303
11304 int
11305 SetPlayer (int player, char *p)
11306 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11307     int i;
11308     char buf[MSG_SIZ], *engineName;
11309     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11310     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11311     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11312     if(mnemonic[i]) {
11313         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11314         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11315         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11316         ParseArgsFromString(buf);
11317     } else { // no engine with this nickname is installed!
11318         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11319         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11320         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11321         ModeHighlight();
11322         DisplayError(buf, 0);
11323         return 0;
11324     }
11325     free(engineName);
11326     return i;
11327 }
11328
11329 char *recentEngines;
11330
11331 void
11332 RecentEngineEvent (int nr)
11333 {
11334     int n;
11335 //    SwapEngines(1); // bump first to second
11336 //    ReplaceEngine(&second, 1); // and load it there
11337     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11338     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11339     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11340         ReplaceEngine(&first, 0);
11341         FloatToFront(&appData.recentEngineList, command[n]);
11342     }
11343 }
11344
11345 int
11346 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11347 {   // determine players from game number
11348     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11349
11350     if(appData.tourneyType == 0) {
11351         roundsPerCycle = (nPlayers - 1) | 1;
11352         pairingsPerRound = nPlayers / 2;
11353     } else if(appData.tourneyType > 0) {
11354         roundsPerCycle = nPlayers - appData.tourneyType;
11355         pairingsPerRound = appData.tourneyType;
11356     }
11357     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11358     gamesPerCycle = gamesPerRound * roundsPerCycle;
11359     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11360     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11361     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11362     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11363     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11364     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11365
11366     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11367     if(appData.roundSync) *syncInterval = gamesPerRound;
11368
11369     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11370
11371     if(appData.tourneyType == 0) {
11372         if(curPairing == (nPlayers-1)/2 ) {
11373             *whitePlayer = curRound;
11374             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11375         } else {
11376             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11377             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11378             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11379             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11380         }
11381     } else if(appData.tourneyType > 1) {
11382         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11383         *whitePlayer = curRound + appData.tourneyType;
11384     } else if(appData.tourneyType > 0) {
11385         *whitePlayer = curPairing;
11386         *blackPlayer = curRound + appData.tourneyType;
11387     }
11388
11389     // take care of white/black alternation per round.
11390     // For cycles and games this is already taken care of by default, derived from matchGame!
11391     return curRound & 1;
11392 }
11393
11394 int
11395 NextTourneyGame (int nr, int *swapColors)
11396 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11397     char *p, *q;
11398     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11399     FILE *tf;
11400     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11401     tf = fopen(appData.tourneyFile, "r");
11402     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11403     ParseArgsFromFile(tf); fclose(tf);
11404     InitTimeControls(); // TC might be altered from tourney file
11405
11406     nPlayers = CountPlayers(appData.participants); // count participants
11407     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11408     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11409
11410     if(syncInterval) {
11411         p = q = appData.results;
11412         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11413         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11414             DisplayMessage(_("Waiting for other game(s)"),"");
11415             waitingForGame = TRUE;
11416             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11417             return 0;
11418         }
11419         waitingForGame = FALSE;
11420     }
11421
11422     if(appData.tourneyType < 0) {
11423         if(nr>=0 && !pairingReceived) {
11424             char buf[1<<16];
11425             if(pairing.pr == NoProc) {
11426                 if(!appData.pairingEngine[0]) {
11427                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11428                     return 0;
11429                 }
11430                 StartChessProgram(&pairing); // starts the pairing engine
11431             }
11432             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11433             SendToProgram(buf, &pairing);
11434             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11435             SendToProgram(buf, &pairing);
11436             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11437         }
11438         pairingReceived = 0;                              // ... so we continue here
11439         *swapColors = 0;
11440         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11441         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11442         matchGame = 1; roundNr = nr / syncInterval + 1;
11443     }
11444
11445     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11446
11447     // redefine engines, engine dir, etc.
11448     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11449     if(first.pr == NoProc) {
11450       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11451       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11452     }
11453     if(second.pr == NoProc) {
11454       SwapEngines(1);
11455       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11456       SwapEngines(1);         // and make that valid for second engine by swapping
11457       InitEngine(&second, 1);
11458     }
11459     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11460     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11461     return OK;
11462 }
11463
11464 void
11465 NextMatchGame ()
11466 {   // performs game initialization that does not invoke engines, and then tries to start the game
11467     int res, firstWhite, swapColors = 0;
11468     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11469     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
11470         char buf[MSG_SIZ];
11471         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11472         if(strcmp(buf, currentDebugFile)) { // name has changed
11473             FILE *f = fopen(buf, "w");
11474             if(f) { // if opening the new file failed, just keep using the old one
11475                 ASSIGN(currentDebugFile, buf);
11476                 fclose(debugFP);
11477                 debugFP = f;
11478             }
11479             if(appData.serverFileName) {
11480                 if(serverFP) fclose(serverFP);
11481                 serverFP = fopen(appData.serverFileName, "w");
11482                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11483                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11484             }
11485         }
11486     }
11487     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11488     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11489     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11490     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11491     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11492     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11493     Reset(FALSE, first.pr != NoProc);
11494     res = LoadGameOrPosition(matchGame); // setup game
11495     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11496     if(!res) return; // abort when bad game/pos file
11497     if(appData.epd) {// in EPD mode we make sure first engine is to move
11498         firstWhite = !(forwardMostMove & 1);
11499         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11500         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11501     }
11502     TwoMachinesEvent();
11503 }
11504
11505 void
11506 UserAdjudicationEvent (int result)
11507 {
11508     ChessMove gameResult = GameIsDrawn;
11509
11510     if( result > 0 ) {
11511         gameResult = WhiteWins;
11512     }
11513     else if( result < 0 ) {
11514         gameResult = BlackWins;
11515     }
11516
11517     if( gameMode == TwoMachinesPlay ) {
11518         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11519     }
11520 }
11521
11522
11523 // [HGM] save: calculate checksum of game to make games easily identifiable
11524 int
11525 StringCheckSum (char *s)
11526 {
11527         int i = 0;
11528         if(s==NULL) return 0;
11529         while(*s) i = i*259 + *s++;
11530         return i;
11531 }
11532
11533 int
11534 GameCheckSum ()
11535 {
11536         int i, sum=0;
11537         for(i=backwardMostMove; i<forwardMostMove; i++) {
11538                 sum += pvInfoList[i].depth;
11539                 sum += StringCheckSum(parseList[i]);
11540                 sum += StringCheckSum(commentList[i]);
11541                 sum *= 261;
11542         }
11543         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11544         return sum + StringCheckSum(commentList[i]);
11545 } // end of save patch
11546
11547 void
11548 GameEnds (ChessMove result, char *resultDetails, int whosays)
11549 {
11550     GameMode nextGameMode;
11551     int isIcsGame;
11552     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11553
11554     if(endingGame) return; /* [HGM] crash: forbid recursion */
11555     endingGame = 1;
11556     if(twoBoards) { // [HGM] dual: switch back to one board
11557         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11558         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11559     }
11560     if (appData.debugMode) {
11561       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11562               result, resultDetails ? resultDetails : "(null)", whosays);
11563     }
11564
11565     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11566
11567     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11568
11569     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11570         /* If we are playing on ICS, the server decides when the
11571            game is over, but the engine can offer to draw, claim
11572            a draw, or resign.
11573          */
11574 #if ZIPPY
11575         if (appData.zippyPlay && first.initDone) {
11576             if (result == GameIsDrawn) {
11577                 /* In case draw still needs to be claimed */
11578                 SendToICS(ics_prefix);
11579                 SendToICS("draw\n");
11580             } else if (StrCaseStr(resultDetails, "resign")) {
11581                 SendToICS(ics_prefix);
11582                 SendToICS("resign\n");
11583             }
11584         }
11585 #endif
11586         endingGame = 0; /* [HGM] crash */
11587         return;
11588     }
11589
11590     /* If we're loading the game from a file, stop */
11591     if (whosays == GE_FILE) {
11592       (void) StopLoadGameTimer();
11593       gameFileFP = NULL;
11594     }
11595
11596     /* Cancel draw offers */
11597     first.offeredDraw = second.offeredDraw = 0;
11598
11599     /* If this is an ICS game, only ICS can really say it's done;
11600        if not, anyone can. */
11601     isIcsGame = (gameMode == IcsPlayingWhite ||
11602                  gameMode == IcsPlayingBlack ||
11603                  gameMode == IcsObserving    ||
11604                  gameMode == IcsExamining);
11605
11606     if (!isIcsGame || whosays == GE_ICS) {
11607         /* OK -- not an ICS game, or ICS said it was done */
11608         StopClocks();
11609         if (!isIcsGame && !appData.noChessProgram)
11610           SetUserThinkingEnables();
11611
11612         /* [HGM] if a machine claims the game end we verify this claim */
11613         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11614             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11615                 char claimer;
11616                 ChessMove trueResult = (ChessMove) -1;
11617
11618                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11619                                             first.twoMachinesColor[0] :
11620                                             second.twoMachinesColor[0] ;
11621
11622                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11623                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11624                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11625                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11626                 } else
11627                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11628                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11629                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11630                 } else
11631                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11632                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11633                 }
11634
11635                 // now verify win claims, but not in drop games, as we don't understand those yet
11636                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11637                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11638                     (result == WhiteWins && claimer == 'w' ||
11639                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11640                       if (appData.debugMode) {
11641                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11642                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11643                       }
11644                       if(result != trueResult) {
11645                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11646                               result = claimer == 'w' ? BlackWins : WhiteWins;
11647                               resultDetails = buf;
11648                       }
11649                 } else
11650                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11651                     && (forwardMostMove <= backwardMostMove ||
11652                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11653                         (claimer=='b')==(forwardMostMove&1))
11654                                                                                   ) {
11655                       /* [HGM] verify: draws that were not flagged are false claims */
11656                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11657                       result = claimer == 'w' ? BlackWins : WhiteWins;
11658                       resultDetails = buf;
11659                 }
11660                 /* (Claiming a loss is accepted no questions asked!) */
11661             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11662                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11663                 result = GameUnfinished;
11664                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11665             }
11666             /* [HGM] bare: don't allow bare King to win */
11667             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11668                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11669                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11670                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11671                && result != GameIsDrawn)
11672             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11673                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11674                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11675                         if(p >= 0 && p <= (int)WhiteKing) k++;
11676                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11677                 }
11678                 if (appData.debugMode) {
11679                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11680                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11681                 }
11682                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11683                         result = GameIsDrawn;
11684                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11685                         resultDetails = buf;
11686                 }
11687             }
11688         }
11689
11690
11691         if(serverMoves != NULL && !loadFlag) { char c = '=';
11692             if(result==WhiteWins) c = '+';
11693             if(result==BlackWins) c = '-';
11694             if(resultDetails != NULL)
11695                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11696         }
11697         if (resultDetails != NULL) {
11698             gameInfo.result = result;
11699             gameInfo.resultDetails = StrSave(resultDetails);
11700
11701             /* display last move only if game was not loaded from file */
11702             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11703                 DisplayMove(currentMove - 1);
11704
11705             if (forwardMostMove != 0) {
11706                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11707                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11708                                                                 ) {
11709                     if (*appData.saveGameFile != NULLCHAR) {
11710                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11711                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11712                         else
11713                         SaveGameToFile(appData.saveGameFile, TRUE);
11714                     } else if (appData.autoSaveGames) {
11715                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11716                     }
11717                     if (*appData.savePositionFile != NULLCHAR) {
11718                         SavePositionToFile(appData.savePositionFile);
11719                     }
11720                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11721                 }
11722             }
11723
11724             /* Tell program how game ended in case it is learning */
11725             /* [HGM] Moved this to after saving the PGN, just in case */
11726             /* engine died and we got here through time loss. In that */
11727             /* case we will get a fatal error writing the pipe, which */
11728             /* would otherwise lose us the PGN.                       */
11729             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11730             /* output during GameEnds should never be fatal anymore   */
11731             if (gameMode == MachinePlaysWhite ||
11732                 gameMode == MachinePlaysBlack ||
11733                 gameMode == TwoMachinesPlay ||
11734                 gameMode == IcsPlayingWhite ||
11735                 gameMode == IcsPlayingBlack ||
11736                 gameMode == BeginningOfGame) {
11737                 char buf[MSG_SIZ];
11738                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11739                         resultDetails);
11740                 if (first.pr != NoProc) {
11741                     SendToProgram(buf, &first);
11742                 }
11743                 if (second.pr != NoProc &&
11744                     gameMode == TwoMachinesPlay) {
11745                     SendToProgram(buf, &second);
11746                 }
11747             }
11748         }
11749
11750         if (appData.icsActive) {
11751             if (appData.quietPlay &&
11752                 (gameMode == IcsPlayingWhite ||
11753                  gameMode == IcsPlayingBlack)) {
11754                 SendToICS(ics_prefix);
11755                 SendToICS("set shout 1\n");
11756             }
11757             nextGameMode = IcsIdle;
11758             ics_user_moved = FALSE;
11759             /* clean up premove.  It's ugly when the game has ended and the
11760              * premove highlights are still on the board.
11761              */
11762             if (gotPremove) {
11763               gotPremove = FALSE;
11764               ClearPremoveHighlights();
11765               DrawPosition(FALSE, boards[currentMove]);
11766             }
11767             if (whosays == GE_ICS) {
11768                 switch (result) {
11769                 case WhiteWins:
11770                     if (gameMode == IcsPlayingWhite)
11771                         PlayIcsWinSound();
11772                     else if(gameMode == IcsPlayingBlack)
11773                         PlayIcsLossSound();
11774                     break;
11775                 case BlackWins:
11776                     if (gameMode == IcsPlayingBlack)
11777                         PlayIcsWinSound();
11778                     else if(gameMode == IcsPlayingWhite)
11779                         PlayIcsLossSound();
11780                     break;
11781                 case GameIsDrawn:
11782                     PlayIcsDrawSound();
11783                     break;
11784                 default:
11785                     PlayIcsUnfinishedSound();
11786                 }
11787             }
11788             if(appData.quitNext) { ExitEvent(0); return; }
11789         } else if (gameMode == EditGame ||
11790                    gameMode == PlayFromGameFile ||
11791                    gameMode == AnalyzeMode ||
11792                    gameMode == AnalyzeFile) {
11793             nextGameMode = gameMode;
11794         } else {
11795             nextGameMode = EndOfGame;
11796         }
11797         pausing = FALSE;
11798         ModeHighlight();
11799     } else {
11800         nextGameMode = gameMode;
11801     }
11802
11803     if (appData.noChessProgram) {
11804         gameMode = nextGameMode;
11805         ModeHighlight();
11806         endingGame = 0; /* [HGM] crash */
11807         return;
11808     }
11809
11810     if (first.reuse) {
11811         /* Put first chess program into idle state */
11812         if (first.pr != NoProc &&
11813             (gameMode == MachinePlaysWhite ||
11814              gameMode == MachinePlaysBlack ||
11815              gameMode == TwoMachinesPlay ||
11816              gameMode == IcsPlayingWhite ||
11817              gameMode == IcsPlayingBlack ||
11818              gameMode == BeginningOfGame)) {
11819             SendToProgram("force\n", &first);
11820             if (first.usePing) {
11821               char buf[MSG_SIZ];
11822               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11823               SendToProgram(buf, &first);
11824             }
11825         }
11826     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11827         /* Kill off first chess program */
11828         if (first.isr != NULL)
11829           RemoveInputSource(first.isr);
11830         first.isr = NULL;
11831
11832         if (first.pr != NoProc) {
11833             ExitAnalyzeMode();
11834             DoSleep( appData.delayBeforeQuit );
11835             SendToProgram("quit\n", &first);
11836             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11837             first.reload = TRUE;
11838         }
11839         first.pr = NoProc;
11840     }
11841     if (second.reuse) {
11842         /* Put second chess program into idle state */
11843         if (second.pr != NoProc &&
11844             gameMode == TwoMachinesPlay) {
11845             SendToProgram("force\n", &second);
11846             if (second.usePing) {
11847               char buf[MSG_SIZ];
11848               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11849               SendToProgram(buf, &second);
11850             }
11851         }
11852     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11853         /* Kill off second chess program */
11854         if (second.isr != NULL)
11855           RemoveInputSource(second.isr);
11856         second.isr = NULL;
11857
11858         if (second.pr != NoProc) {
11859             DoSleep( appData.delayBeforeQuit );
11860             SendToProgram("quit\n", &second);
11861             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11862             second.reload = TRUE;
11863         }
11864         second.pr = NoProc;
11865     }
11866
11867     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11868         char resChar = '=';
11869         switch (result) {
11870         case WhiteWins:
11871           resChar = '+';
11872           if (first.twoMachinesColor[0] == 'w') {
11873             first.matchWins++;
11874           } else {
11875             second.matchWins++;
11876           }
11877           break;
11878         case BlackWins:
11879           resChar = '-';
11880           if (first.twoMachinesColor[0] == 'b') {
11881             first.matchWins++;
11882           } else {
11883             second.matchWins++;
11884           }
11885           break;
11886         case GameUnfinished:
11887           resChar = ' ';
11888         default:
11889           break;
11890         }
11891
11892         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11893         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11894             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11895             ReserveGame(nextGame, resChar); // sets nextGame
11896             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11897             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11898         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11899
11900         if (nextGame <= appData.matchGames && !abortMatch) {
11901             gameMode = nextGameMode;
11902             matchGame = nextGame; // this will be overruled in tourney mode!
11903             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11904             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11905             endingGame = 0; /* [HGM] crash */
11906             return;
11907         } else {
11908             gameMode = nextGameMode;
11909             if(appData.epd) {
11910                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11911                 OutputKibitz(2, buf);
11912                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11913                 OutputKibitz(2, buf);
11914                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11915                 if(second.matchWins) OutputKibitz(2, buf);
11916                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11917                 OutputKibitz(2, buf);
11918             }
11919             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11920                      first.tidy, second.tidy,
11921                      first.matchWins, second.matchWins,
11922                      appData.matchGames - (first.matchWins + second.matchWins));
11923             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11924             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11925             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11926             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11927                 first.twoMachinesColor = "black\n";
11928                 second.twoMachinesColor = "white\n";
11929             } else {
11930                 first.twoMachinesColor = "white\n";
11931                 second.twoMachinesColor = "black\n";
11932             }
11933         }
11934     }
11935     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11936         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11937       ExitAnalyzeMode();
11938     gameMode = nextGameMode;
11939     ModeHighlight();
11940     endingGame = 0;  /* [HGM] crash */
11941     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11942         if(matchMode == TRUE) { // match through command line: exit with or without popup
11943             if(ranking) {
11944                 ToNrEvent(forwardMostMove);
11945                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11946                 else ExitEvent(0);
11947             } else DisplayFatalError(buf, 0, 0);
11948         } else { // match through menu; just stop, with or without popup
11949             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11950             ModeHighlight();
11951             if(ranking){
11952                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11953             } else DisplayNote(buf);
11954       }
11955       if(ranking) free(ranking);
11956     }
11957 }
11958
11959 /* Assumes program was just initialized (initString sent).
11960    Leaves program in force mode. */
11961 void
11962 FeedMovesToProgram (ChessProgramState *cps, int upto)
11963 {
11964     int i;
11965
11966     if (appData.debugMode)
11967       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11968               startedFromSetupPosition ? "position and " : "",
11969               backwardMostMove, upto, cps->which);
11970     if(currentlyInitializedVariant != gameInfo.variant) {
11971       char buf[MSG_SIZ];
11972         // [HGM] variantswitch: make engine aware of new variant
11973         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11974                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11975                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11976         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11977         SendToProgram(buf, cps);
11978         currentlyInitializedVariant = gameInfo.variant;
11979     }
11980     SendToProgram("force\n", cps);
11981     if (startedFromSetupPosition) {
11982         SendBoard(cps, backwardMostMove);
11983     if (appData.debugMode) {
11984         fprintf(debugFP, "feedMoves\n");
11985     }
11986     }
11987     for (i = backwardMostMove; i < upto; i++) {
11988         SendMoveToProgram(i, cps);
11989     }
11990 }
11991
11992
11993 int
11994 ResurrectChessProgram ()
11995 {
11996      /* The chess program may have exited.
11997         If so, restart it and feed it all the moves made so far. */
11998     static int doInit = 0;
11999
12000     if (appData.noChessProgram) return 1;
12001
12002     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12003         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12004         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12005         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12006     } else {
12007         if (first.pr != NoProc) return 1;
12008         StartChessProgram(&first);
12009     }
12010     InitChessProgram(&first, FALSE);
12011     FeedMovesToProgram(&first, currentMove);
12012
12013     if (!first.sendTime) {
12014         /* can't tell gnuchess what its clock should read,
12015            so we bow to its notion. */
12016         ResetClocks();
12017         timeRemaining[0][currentMove] = whiteTimeRemaining;
12018         timeRemaining[1][currentMove] = blackTimeRemaining;
12019     }
12020
12021     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12022                 appData.icsEngineAnalyze) && first.analysisSupport) {
12023       SendToProgram("analyze\n", &first);
12024       first.analyzing = TRUE;
12025     }
12026     return 1;
12027 }
12028
12029 /*
12030  * Button procedures
12031  */
12032 void
12033 Reset (int redraw, int init)
12034 {
12035     int i;
12036
12037     if (appData.debugMode) {
12038         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12039                 redraw, init, gameMode);
12040     }
12041     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12042     deadRanks = 0; // assume entire board is used
12043     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12044     CleanupTail(); // [HGM] vari: delete any stored variations
12045     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12046     pausing = pauseExamInvalid = FALSE;
12047     startedFromSetupPosition = blackPlaysFirst = FALSE;
12048     firstMove = TRUE;
12049     whiteFlag = blackFlag = FALSE;
12050     userOfferedDraw = FALSE;
12051     hintRequested = bookRequested = FALSE;
12052     first.maybeThinking = FALSE;
12053     second.maybeThinking = FALSE;
12054     first.bookSuspend = FALSE; // [HGM] book
12055     second.bookSuspend = FALSE;
12056     thinkOutput[0] = NULLCHAR;
12057     lastHint[0] = NULLCHAR;
12058     ClearGameInfo(&gameInfo);
12059     gameInfo.variant = StringToVariant(appData.variant);
12060     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12061         gameInfo.variant = VariantUnknown;
12062         strncpy(engineVariant, appData.variant, MSG_SIZ);
12063     }
12064     ics_user_moved = ics_clock_paused = FALSE;
12065     ics_getting_history = H_FALSE;
12066     ics_gamenum = -1;
12067     white_holding[0] = black_holding[0] = NULLCHAR;
12068     ClearProgramStats();
12069     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12070
12071     ResetFrontEnd();
12072     ClearHighlights();
12073     flipView = appData.flipView;
12074     ClearPremoveHighlights();
12075     gotPremove = FALSE;
12076     alarmSounded = FALSE;
12077     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12078
12079     GameEnds(EndOfFile, NULL, GE_PLAYER);
12080     if(appData.serverMovesName != NULL) {
12081         /* [HGM] prepare to make moves file for broadcasting */
12082         clock_t t = clock();
12083         if(serverMoves != NULL) fclose(serverMoves);
12084         serverMoves = fopen(appData.serverMovesName, "r");
12085         if(serverMoves != NULL) {
12086             fclose(serverMoves);
12087             /* delay 15 sec before overwriting, so all clients can see end */
12088             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12089         }
12090         serverMoves = fopen(appData.serverMovesName, "w");
12091     }
12092
12093     ExitAnalyzeMode();
12094     gameMode = BeginningOfGame;
12095     ModeHighlight();
12096     if(appData.icsActive) gameInfo.variant = VariantNormal;
12097     currentMove = forwardMostMove = backwardMostMove = 0;
12098     MarkTargetSquares(1);
12099     InitPosition(redraw);
12100     for (i = 0; i < MAX_MOVES; i++) {
12101         if (commentList[i] != NULL) {
12102             free(commentList[i]);
12103             commentList[i] = NULL;
12104         }
12105     }
12106     ResetClocks();
12107     timeRemaining[0][0] = whiteTimeRemaining;
12108     timeRemaining[1][0] = blackTimeRemaining;
12109
12110     if (first.pr == NoProc) {
12111         StartChessProgram(&first);
12112     }
12113     if (init) {
12114             InitChessProgram(&first, startedFromSetupPosition);
12115     }
12116     DisplayTitle("");
12117     DisplayMessage("", "");
12118     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12119     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12120     ClearMap();        // [HGM] exclude: invalidate map
12121 }
12122
12123 void
12124 AutoPlayGameLoop ()
12125 {
12126     for (;;) {
12127         if (!AutoPlayOneMove())
12128           return;
12129         if (matchMode || appData.timeDelay == 0)
12130           continue;
12131         if (appData.timeDelay < 0)
12132           return;
12133         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12134         break;
12135     }
12136 }
12137
12138 void
12139 AnalyzeNextGame()
12140 {
12141     ReloadGame(1); // next game
12142 }
12143
12144 int
12145 AutoPlayOneMove ()
12146 {
12147     int fromX, fromY, toX, toY;
12148
12149     if (appData.debugMode) {
12150       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12151     }
12152
12153     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12154       return FALSE;
12155
12156     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12157       pvInfoList[currentMove].depth = programStats.depth;
12158       pvInfoList[currentMove].score = programStats.score;
12159       pvInfoList[currentMove].time  = 0;
12160       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12161       else { // append analysis of final position as comment
12162         char buf[MSG_SIZ];
12163         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12164         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12165       }
12166       programStats.depth = 0;
12167     }
12168
12169     if (currentMove >= forwardMostMove) {
12170       if(gameMode == AnalyzeFile) {
12171           if(appData.loadGameIndex == -1) {
12172             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12173           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12174           } else {
12175           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12176         }
12177       }
12178 //      gameMode = EndOfGame;
12179 //      ModeHighlight();
12180
12181       /* [AS] Clear current move marker at the end of a game */
12182       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12183
12184       return FALSE;
12185     }
12186
12187     toX = moveList[currentMove][2] - AAA;
12188     toY = moveList[currentMove][3] - ONE;
12189
12190     if (moveList[currentMove][1] == '@') {
12191         if (appData.highlightLastMove) {
12192             SetHighlights(-1, -1, toX, toY);
12193         }
12194     } else {
12195         fromX = moveList[currentMove][0] - AAA;
12196         fromY = moveList[currentMove][1] - ONE;
12197
12198         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12199
12200         if(moveList[currentMove][4] == ';') { // multi-leg
12201             killX = moveList[currentMove][5] - AAA;
12202             killY = moveList[currentMove][6] - ONE;
12203         }
12204         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12205         killX = killY = -1;
12206
12207         if (appData.highlightLastMove) {
12208             SetHighlights(fromX, fromY, toX, toY);
12209         }
12210     }
12211     DisplayMove(currentMove);
12212     SendMoveToProgram(currentMove++, &first);
12213     DisplayBothClocks();
12214     DrawPosition(FALSE, boards[currentMove]);
12215     // [HGM] PV info: always display, routine tests if empty
12216     DisplayComment(currentMove - 1, commentList[currentMove]);
12217     return TRUE;
12218 }
12219
12220
12221 int
12222 LoadGameOneMove (ChessMove readAhead)
12223 {
12224     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12225     char promoChar = NULLCHAR;
12226     ChessMove moveType;
12227     char move[MSG_SIZ];
12228     char *p, *q;
12229
12230     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12231         gameMode != AnalyzeMode && gameMode != Training) {
12232         gameFileFP = NULL;
12233         return FALSE;
12234     }
12235
12236     yyboardindex = forwardMostMove;
12237     if (readAhead != EndOfFile) {
12238       moveType = readAhead;
12239     } else {
12240       if (gameFileFP == NULL)
12241           return FALSE;
12242       moveType = (ChessMove) Myylex();
12243     }
12244
12245     done = FALSE;
12246     switch (moveType) {
12247       case Comment:
12248         if (appData.debugMode)
12249           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12250         p = yy_text;
12251
12252         /* append the comment but don't display it */
12253         AppendComment(currentMove, p, FALSE);
12254         return TRUE;
12255
12256       case WhiteCapturesEnPassant:
12257       case BlackCapturesEnPassant:
12258       case WhitePromotion:
12259       case BlackPromotion:
12260       case WhiteNonPromotion:
12261       case BlackNonPromotion:
12262       case NormalMove:
12263       case FirstLeg:
12264       case WhiteKingSideCastle:
12265       case WhiteQueenSideCastle:
12266       case BlackKingSideCastle:
12267       case BlackQueenSideCastle:
12268       case WhiteKingSideCastleWild:
12269       case WhiteQueenSideCastleWild:
12270       case BlackKingSideCastleWild:
12271       case BlackQueenSideCastleWild:
12272       /* PUSH Fabien */
12273       case WhiteHSideCastleFR:
12274       case WhiteASideCastleFR:
12275       case BlackHSideCastleFR:
12276       case BlackASideCastleFR:
12277       /* POP Fabien */
12278         if (appData.debugMode)
12279           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12280         fromX = currentMoveString[0] - AAA;
12281         fromY = currentMoveString[1] - ONE;
12282         toX = currentMoveString[2] - AAA;
12283         toY = currentMoveString[3] - ONE;
12284         promoChar = currentMoveString[4];
12285         if(promoChar == ';') promoChar = currentMoveString[7];
12286         break;
12287
12288       case WhiteDrop:
12289       case BlackDrop:
12290         if (appData.debugMode)
12291           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12292         fromX = moveType == WhiteDrop ?
12293           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12294         (int) CharToPiece(ToLower(currentMoveString[0]));
12295         fromY = DROP_RANK;
12296         toX = currentMoveString[2] - AAA;
12297         toY = currentMoveString[3] - ONE;
12298         break;
12299
12300       case WhiteWins:
12301       case BlackWins:
12302       case GameIsDrawn:
12303       case GameUnfinished:
12304         if (appData.debugMode)
12305           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12306         p = strchr(yy_text, '{');
12307         if (p == NULL) p = strchr(yy_text, '(');
12308         if (p == NULL) {
12309             p = yy_text;
12310             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12311         } else {
12312             q = strchr(p, *p == '{' ? '}' : ')');
12313             if (q != NULL) *q = NULLCHAR;
12314             p++;
12315         }
12316         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12317         GameEnds(moveType, p, GE_FILE);
12318         done = TRUE;
12319         if (cmailMsgLoaded) {
12320             ClearHighlights();
12321             flipView = WhiteOnMove(currentMove);
12322             if (moveType == GameUnfinished) flipView = !flipView;
12323             if (appData.debugMode)
12324               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12325         }
12326         break;
12327
12328       case EndOfFile:
12329         if (appData.debugMode)
12330           fprintf(debugFP, "Parser hit end of file\n");
12331         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12332           case MT_NONE:
12333           case MT_CHECK:
12334             break;
12335           case MT_CHECKMATE:
12336           case MT_STAINMATE:
12337             if (WhiteOnMove(currentMove)) {
12338                 GameEnds(BlackWins, "Black mates", GE_FILE);
12339             } else {
12340                 GameEnds(WhiteWins, "White mates", GE_FILE);
12341             }
12342             break;
12343           case MT_STALEMATE:
12344             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12345             break;
12346         }
12347         done = TRUE;
12348         break;
12349
12350       case MoveNumberOne:
12351         if (lastLoadGameStart == GNUChessGame) {
12352             /* GNUChessGames have numbers, but they aren't move numbers */
12353             if (appData.debugMode)
12354               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12355                       yy_text, (int) moveType);
12356             return LoadGameOneMove(EndOfFile); /* tail recursion */
12357         }
12358         /* else fall thru */
12359
12360       case XBoardGame:
12361       case GNUChessGame:
12362       case PGNTag:
12363         /* Reached start of next game in file */
12364         if (appData.debugMode)
12365           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12366         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12367           case MT_NONE:
12368           case MT_CHECK:
12369             break;
12370           case MT_CHECKMATE:
12371           case MT_STAINMATE:
12372             if (WhiteOnMove(currentMove)) {
12373                 GameEnds(BlackWins, "Black mates", GE_FILE);
12374             } else {
12375                 GameEnds(WhiteWins, "White mates", GE_FILE);
12376             }
12377             break;
12378           case MT_STALEMATE:
12379             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12380             break;
12381         }
12382         done = TRUE;
12383         break;
12384
12385       case PositionDiagram:     /* should not happen; ignore */
12386       case ElapsedTime:         /* ignore */
12387       case NAG:                 /* ignore */
12388         if (appData.debugMode)
12389           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12390                   yy_text, (int) moveType);
12391         return LoadGameOneMove(EndOfFile); /* tail recursion */
12392
12393       case IllegalMove:
12394         if (appData.testLegality) {
12395             if (appData.debugMode)
12396               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12397             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12398                     (forwardMostMove / 2) + 1,
12399                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12400             DisplayError(move, 0);
12401             done = TRUE;
12402         } else {
12403             if (appData.debugMode)
12404               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12405                       yy_text, currentMoveString);
12406             if(currentMoveString[1] == '@') {
12407                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12408                 fromY = DROP_RANK;
12409             } else {
12410                 fromX = currentMoveString[0] - AAA;
12411                 fromY = currentMoveString[1] - ONE;
12412             }
12413             toX = currentMoveString[2] - AAA;
12414             toY = currentMoveString[3] - ONE;
12415             promoChar = currentMoveString[4];
12416         }
12417         break;
12418
12419       case AmbiguousMove:
12420         if (appData.debugMode)
12421           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12422         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12423                 (forwardMostMove / 2) + 1,
12424                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12425         DisplayError(move, 0);
12426         done = TRUE;
12427         break;
12428
12429       default:
12430       case ImpossibleMove:
12431         if (appData.debugMode)
12432           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12433         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12434                 (forwardMostMove / 2) + 1,
12435                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12436         DisplayError(move, 0);
12437         done = TRUE;
12438         break;
12439     }
12440
12441     if (done) {
12442         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12443             DrawPosition(FALSE, boards[currentMove]);
12444             DisplayBothClocks();
12445             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12446               DisplayComment(currentMove - 1, commentList[currentMove]);
12447         }
12448         (void) StopLoadGameTimer();
12449         gameFileFP = NULL;
12450         cmailOldMove = forwardMostMove;
12451         return FALSE;
12452     } else {
12453         /* currentMoveString is set as a side-effect of yylex */
12454
12455         thinkOutput[0] = NULLCHAR;
12456         MakeMove(fromX, fromY, toX, toY, promoChar);
12457         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12458         currentMove = forwardMostMove;
12459         return TRUE;
12460     }
12461 }
12462
12463 /* Load the nth game from the given file */
12464 int
12465 LoadGameFromFile (char *filename, int n, char *title, int useList)
12466 {
12467     FILE *f;
12468     char buf[MSG_SIZ];
12469
12470     if (strcmp(filename, "-") == 0) {
12471         f = stdin;
12472         title = "stdin";
12473     } else {
12474         f = fopen(filename, "rb");
12475         if (f == NULL) {
12476           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12477             DisplayError(buf, errno);
12478             return FALSE;
12479         }
12480     }
12481     if (fseek(f, 0, 0) == -1) {
12482         /* f is not seekable; probably a pipe */
12483         useList = FALSE;
12484     }
12485     if (useList && n == 0) {
12486         int error = GameListBuild(f);
12487         if (error) {
12488             DisplayError(_("Cannot build game list"), error);
12489         } else if (!ListEmpty(&gameList) &&
12490                    ((ListGame *) gameList.tailPred)->number > 1) {
12491             GameListPopUp(f, title);
12492             return TRUE;
12493         }
12494         GameListDestroy();
12495         n = 1;
12496     }
12497     if (n == 0) n = 1;
12498     return LoadGame(f, n, title, FALSE);
12499 }
12500
12501
12502 void
12503 MakeRegisteredMove ()
12504 {
12505     int fromX, fromY, toX, toY;
12506     char promoChar;
12507     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12508         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12509           case CMAIL_MOVE:
12510           case CMAIL_DRAW:
12511             if (appData.debugMode)
12512               fprintf(debugFP, "Restoring %s for game %d\n",
12513                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12514
12515             thinkOutput[0] = NULLCHAR;
12516             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12517             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12518             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12519             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12520             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12521             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12522             MakeMove(fromX, fromY, toX, toY, promoChar);
12523             ShowMove(fromX, fromY, toX, toY);
12524
12525             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12526               case MT_NONE:
12527               case MT_CHECK:
12528                 break;
12529
12530               case MT_CHECKMATE:
12531               case MT_STAINMATE:
12532                 if (WhiteOnMove(currentMove)) {
12533                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12534                 } else {
12535                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12536                 }
12537                 break;
12538
12539               case MT_STALEMATE:
12540                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12541                 break;
12542             }
12543
12544             break;
12545
12546           case CMAIL_RESIGN:
12547             if (WhiteOnMove(currentMove)) {
12548                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12549             } else {
12550                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12551             }
12552             break;
12553
12554           case CMAIL_ACCEPT:
12555             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12556             break;
12557
12558           default:
12559             break;
12560         }
12561     }
12562
12563     return;
12564 }
12565
12566 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12567 int
12568 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12569 {
12570     int retVal;
12571
12572     if (gameNumber > nCmailGames) {
12573         DisplayError(_("No more games in this message"), 0);
12574         return FALSE;
12575     }
12576     if (f == lastLoadGameFP) {
12577         int offset = gameNumber - lastLoadGameNumber;
12578         if (offset == 0) {
12579             cmailMsg[0] = NULLCHAR;
12580             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12581                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12582                 nCmailMovesRegistered--;
12583             }
12584             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12585             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12586                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12587             }
12588         } else {
12589             if (! RegisterMove()) return FALSE;
12590         }
12591     }
12592
12593     retVal = LoadGame(f, gameNumber, title, useList);
12594
12595     /* Make move registered during previous look at this game, if any */
12596     MakeRegisteredMove();
12597
12598     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12599         commentList[currentMove]
12600           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12601         DisplayComment(currentMove - 1, commentList[currentMove]);
12602     }
12603
12604     return retVal;
12605 }
12606
12607 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12608 int
12609 ReloadGame (int offset)
12610 {
12611     int gameNumber = lastLoadGameNumber + offset;
12612     if (lastLoadGameFP == NULL) {
12613         DisplayError(_("No game has been loaded yet"), 0);
12614         return FALSE;
12615     }
12616     if (gameNumber <= 0) {
12617         DisplayError(_("Can't back up any further"), 0);
12618         return FALSE;
12619     }
12620     if (cmailMsgLoaded) {
12621         return CmailLoadGame(lastLoadGameFP, gameNumber,
12622                              lastLoadGameTitle, lastLoadGameUseList);
12623     } else {
12624         return LoadGame(lastLoadGameFP, gameNumber,
12625                         lastLoadGameTitle, lastLoadGameUseList);
12626     }
12627 }
12628
12629 int keys[EmptySquare+1];
12630
12631 int
12632 PositionMatches (Board b1, Board b2)
12633 {
12634     int r, f, sum=0;
12635     switch(appData.searchMode) {
12636         case 1: return CompareWithRights(b1, b2);
12637         case 2:
12638             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12639                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12640             }
12641             return TRUE;
12642         case 3:
12643             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12644               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12645                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12646             }
12647             return sum==0;
12648         case 4:
12649             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12650                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12651             }
12652             return sum==0;
12653     }
12654     return TRUE;
12655 }
12656
12657 #define Q_PROMO  4
12658 #define Q_EP     3
12659 #define Q_BCASTL 2
12660 #define Q_WCASTL 1
12661
12662 int pieceList[256], quickBoard[256];
12663 ChessSquare pieceType[256] = { EmptySquare };
12664 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12665 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12666 int soughtTotal, turn;
12667 Boolean epOK, flipSearch;
12668
12669 typedef struct {
12670     unsigned char piece, to;
12671 } Move;
12672
12673 #define DSIZE (250000)
12674
12675 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12676 Move *moveDatabase = initialSpace;
12677 unsigned int movePtr, dataSize = DSIZE;
12678
12679 int
12680 MakePieceList (Board board, int *counts)
12681 {
12682     int r, f, n=Q_PROMO, total=0;
12683     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12684     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12685         int sq = f + (r<<4);
12686         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12687             quickBoard[sq] = ++n;
12688             pieceList[n] = sq;
12689             pieceType[n] = board[r][f];
12690             counts[board[r][f]]++;
12691             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12692             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12693             total++;
12694         }
12695     }
12696     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12697     return total;
12698 }
12699
12700 void
12701 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12702 {
12703     int sq = fromX + (fromY<<4);
12704     int piece = quickBoard[sq], rook;
12705     quickBoard[sq] = 0;
12706     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12707     if(piece == pieceList[1] && fromY == toY) {
12708       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12709         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12710         moveDatabase[movePtr++].piece = Q_WCASTL;
12711         quickBoard[sq] = piece;
12712         piece = quickBoard[from]; quickBoard[from] = 0;
12713         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12714       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12715         quickBoard[sq] = 0; // remove Rook
12716         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12717         moveDatabase[movePtr++].piece = Q_WCASTL;
12718         quickBoard[sq] = pieceList[1]; // put King
12719         piece = rook;
12720         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12721       }
12722     } else
12723     if(piece == pieceList[2] && fromY == toY) {
12724       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12725         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12726         moveDatabase[movePtr++].piece = Q_BCASTL;
12727         quickBoard[sq] = piece;
12728         piece = quickBoard[from]; quickBoard[from] = 0;
12729         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12730       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12731         quickBoard[sq] = 0; // remove Rook
12732         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12733         moveDatabase[movePtr++].piece = Q_BCASTL;
12734         quickBoard[sq] = pieceList[2]; // put King
12735         piece = rook;
12736         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12737       }
12738     } else
12739     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12740         quickBoard[(fromY<<4)+toX] = 0;
12741         moveDatabase[movePtr].piece = Q_EP;
12742         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12743         moveDatabase[movePtr].to = sq;
12744     } else
12745     if(promoPiece != pieceType[piece]) {
12746         moveDatabase[movePtr++].piece = Q_PROMO;
12747         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12748     }
12749     moveDatabase[movePtr].piece = piece;
12750     quickBoard[sq] = piece;
12751     movePtr++;
12752 }
12753
12754 int
12755 PackGame (Board board)
12756 {
12757     Move *newSpace = NULL;
12758     moveDatabase[movePtr].piece = 0; // terminate previous game
12759     if(movePtr > dataSize) {
12760         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12761         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12762         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12763         if(newSpace) {
12764             int i;
12765             Move *p = moveDatabase, *q = newSpace;
12766             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12767             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12768             moveDatabase = newSpace;
12769         } else { // calloc failed, we must be out of memory. Too bad...
12770             dataSize = 0; // prevent calloc events for all subsequent games
12771             return 0;     // and signal this one isn't cached
12772         }
12773     }
12774     movePtr++;
12775     MakePieceList(board, counts);
12776     return movePtr;
12777 }
12778
12779 int
12780 QuickCompare (Board board, int *minCounts, int *maxCounts)
12781 {   // compare according to search mode
12782     int r, f;
12783     switch(appData.searchMode)
12784     {
12785       case 1: // exact position match
12786         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12787         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12788             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12789         }
12790         break;
12791       case 2: // can have extra material on empty squares
12792         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12793             if(board[r][f] == EmptySquare) continue;
12794             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12795         }
12796         break;
12797       case 3: // material with exact Pawn structure
12798         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12799             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12800             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12801         } // fall through to material comparison
12802       case 4: // exact material
12803         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12804         break;
12805       case 6: // material range with given imbalance
12806         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12807         // fall through to range comparison
12808       case 5: // material range
12809         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12810     }
12811     return TRUE;
12812 }
12813
12814 int
12815 QuickScan (Board board, Move *move)
12816 {   // reconstruct game,and compare all positions in it
12817     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12818     do {
12819         int piece = move->piece;
12820         int to = move->to, from = pieceList[piece];
12821         if(found < 0) { // if already found just scan to game end for final piece count
12822           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12823            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12824            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12825                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12826             ) {
12827             static int lastCounts[EmptySquare+1];
12828             int i;
12829             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12830             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12831           } else stretch = 0;
12832           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12833           if(found >= 0 && !appData.minPieces) return found;
12834         }
12835         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12836           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12837           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12838             piece = (++move)->piece;
12839             from = pieceList[piece];
12840             counts[pieceType[piece]]--;
12841             pieceType[piece] = (ChessSquare) move->to;
12842             counts[move->to]++;
12843           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12844             counts[pieceType[quickBoard[to]]]--;
12845             quickBoard[to] = 0; total--;
12846             move++;
12847             continue;
12848           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12849             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12850             from  = pieceList[piece]; // so this must be King
12851             quickBoard[from] = 0;
12852             pieceList[piece] = to;
12853             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12854             quickBoard[from] = 0; // rook
12855             quickBoard[to] = piece;
12856             to = move->to; piece = move->piece;
12857             goto aftercastle;
12858           }
12859         }
12860         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12861         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12862         quickBoard[from] = 0;
12863       aftercastle:
12864         quickBoard[to] = piece;
12865         pieceList[piece] = to;
12866         cnt++; turn ^= 3;
12867         move++;
12868     } while(1);
12869 }
12870
12871 void
12872 InitSearch ()
12873 {
12874     int r, f;
12875     flipSearch = FALSE;
12876     CopyBoard(soughtBoard, boards[currentMove]);
12877     soughtTotal = MakePieceList(soughtBoard, maxSought);
12878     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12879     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12880     CopyBoard(reverseBoard, boards[currentMove]);
12881     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12882         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12883         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12884         reverseBoard[r][f] = piece;
12885     }
12886     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12887     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12888     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12889                  || (boards[currentMove][CASTLING][2] == NoRights ||
12890                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12891                  && (boards[currentMove][CASTLING][5] == NoRights ||
12892                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12893       ) {
12894         flipSearch = TRUE;
12895         CopyBoard(flipBoard, soughtBoard);
12896         CopyBoard(rotateBoard, reverseBoard);
12897         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12898             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12899             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12900         }
12901     }
12902     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12903     if(appData.searchMode >= 5) {
12904         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12905         MakePieceList(soughtBoard, minSought);
12906         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12907     }
12908     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12909         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12910 }
12911
12912 GameInfo dummyInfo;
12913 static int creatingBook;
12914
12915 int
12916 GameContainsPosition (FILE *f, ListGame *lg)
12917 {
12918     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12919     int fromX, fromY, toX, toY;
12920     char promoChar;
12921     static int initDone=FALSE;
12922
12923     // weed out games based on numerical tag comparison
12924     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12925     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12926     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12927     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12928     if(!initDone) {
12929         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12930         initDone = TRUE;
12931     }
12932     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12933     else CopyBoard(boards[scratch], initialPosition); // default start position
12934     if(lg->moves) {
12935         turn = btm + 1;
12936         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12937         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12938     }
12939     if(btm) plyNr++;
12940     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12941     fseek(f, lg->offset, 0);
12942     yynewfile(f);
12943     while(1) {
12944         yyboardindex = scratch;
12945         quickFlag = plyNr+1;
12946         next = Myylex();
12947         quickFlag = 0;
12948         switch(next) {
12949             case PGNTag:
12950                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12951             default:
12952                 continue;
12953
12954             case XBoardGame:
12955             case GNUChessGame:
12956                 if(plyNr) return -1; // after we have seen moves, this is for new game
12957               continue;
12958
12959             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12960             case ImpossibleMove:
12961             case WhiteWins: // game ends here with these four
12962             case BlackWins:
12963             case GameIsDrawn:
12964             case GameUnfinished:
12965                 return -1;
12966
12967             case IllegalMove:
12968                 if(appData.testLegality) return -1;
12969             case WhiteCapturesEnPassant:
12970             case BlackCapturesEnPassant:
12971             case WhitePromotion:
12972             case BlackPromotion:
12973             case WhiteNonPromotion:
12974             case BlackNonPromotion:
12975             case NormalMove:
12976             case FirstLeg:
12977             case WhiteKingSideCastle:
12978             case WhiteQueenSideCastle:
12979             case BlackKingSideCastle:
12980             case BlackQueenSideCastle:
12981             case WhiteKingSideCastleWild:
12982             case WhiteQueenSideCastleWild:
12983             case BlackKingSideCastleWild:
12984             case BlackQueenSideCastleWild:
12985             case WhiteHSideCastleFR:
12986             case WhiteASideCastleFR:
12987             case BlackHSideCastleFR:
12988             case BlackASideCastleFR:
12989                 fromX = currentMoveString[0] - AAA;
12990                 fromY = currentMoveString[1] - ONE;
12991                 toX = currentMoveString[2] - AAA;
12992                 toY = currentMoveString[3] - ONE;
12993                 promoChar = currentMoveString[4];
12994                 break;
12995             case WhiteDrop:
12996             case BlackDrop:
12997                 fromX = next == WhiteDrop ?
12998                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12999                   (int) CharToPiece(ToLower(currentMoveString[0]));
13000                 fromY = DROP_RANK;
13001                 toX = currentMoveString[2] - AAA;
13002                 toY = currentMoveString[3] - ONE;
13003                 promoChar = 0;
13004                 break;
13005         }
13006         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13007         plyNr++;
13008         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13009         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13010         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13011         if(appData.findMirror) {
13012             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13013             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13014         }
13015     }
13016 }
13017
13018 /* Load the nth game from open file f */
13019 int
13020 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13021 {
13022     ChessMove cm;
13023     char buf[MSG_SIZ];
13024     int gn = gameNumber;
13025     ListGame *lg = NULL;
13026     int numPGNTags = 0, i;
13027     int err, pos = -1;
13028     GameMode oldGameMode;
13029     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13030     char oldName[MSG_SIZ];
13031
13032     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13033
13034     if (appData.debugMode)
13035         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13036
13037     if (gameMode == Training )
13038         SetTrainingModeOff();
13039
13040     oldGameMode = gameMode;
13041     if (gameMode != BeginningOfGame) {
13042       Reset(FALSE, TRUE);
13043     }
13044     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13045
13046     gameFileFP = f;
13047     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13048         fclose(lastLoadGameFP);
13049     }
13050
13051     if (useList) {
13052         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13053
13054         if (lg) {
13055             fseek(f, lg->offset, 0);
13056             GameListHighlight(gameNumber);
13057             pos = lg->position;
13058             gn = 1;
13059         }
13060         else {
13061             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13062               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13063             else
13064             DisplayError(_("Game number out of range"), 0);
13065             return FALSE;
13066         }
13067     } else {
13068         GameListDestroy();
13069         if (fseek(f, 0, 0) == -1) {
13070             if (f == lastLoadGameFP ?
13071                 gameNumber == lastLoadGameNumber + 1 :
13072                 gameNumber == 1) {
13073                 gn = 1;
13074             } else {
13075                 DisplayError(_("Can't seek on game file"), 0);
13076                 return FALSE;
13077             }
13078         }
13079     }
13080     lastLoadGameFP = f;
13081     lastLoadGameNumber = gameNumber;
13082     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13083     lastLoadGameUseList = useList;
13084
13085     yynewfile(f);
13086
13087     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13088       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13089                 lg->gameInfo.black);
13090             DisplayTitle(buf);
13091     } else if (*title != NULLCHAR) {
13092         if (gameNumber > 1) {
13093           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13094             DisplayTitle(buf);
13095         } else {
13096             DisplayTitle(title);
13097         }
13098     }
13099
13100     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13101         gameMode = PlayFromGameFile;
13102         ModeHighlight();
13103     }
13104
13105     currentMove = forwardMostMove = backwardMostMove = 0;
13106     CopyBoard(boards[0], initialPosition);
13107     StopClocks();
13108
13109     /*
13110      * Skip the first gn-1 games in the file.
13111      * Also skip over anything that precedes an identifiable
13112      * start of game marker, to avoid being confused by
13113      * garbage at the start of the file.  Currently
13114      * recognized start of game markers are the move number "1",
13115      * the pattern "gnuchess .* game", the pattern
13116      * "^[#;%] [^ ]* game file", and a PGN tag block.
13117      * A game that starts with one of the latter two patterns
13118      * will also have a move number 1, possibly
13119      * following a position diagram.
13120      * 5-4-02: Let's try being more lenient and allowing a game to
13121      * start with an unnumbered move.  Does that break anything?
13122      */
13123     cm = lastLoadGameStart = EndOfFile;
13124     while (gn > 0) {
13125         yyboardindex = forwardMostMove;
13126         cm = (ChessMove) Myylex();
13127         switch (cm) {
13128           case EndOfFile:
13129             if (cmailMsgLoaded) {
13130                 nCmailGames = CMAIL_MAX_GAMES - gn;
13131             } else {
13132                 Reset(TRUE, TRUE);
13133                 DisplayError(_("Game not found in file"), 0);
13134             }
13135             return FALSE;
13136
13137           case GNUChessGame:
13138           case XBoardGame:
13139             gn--;
13140             lastLoadGameStart = cm;
13141             break;
13142
13143           case MoveNumberOne:
13144             switch (lastLoadGameStart) {
13145               case GNUChessGame:
13146               case XBoardGame:
13147               case PGNTag:
13148                 break;
13149               case MoveNumberOne:
13150               case EndOfFile:
13151                 gn--;           /* count this game */
13152                 lastLoadGameStart = cm;
13153                 break;
13154               default:
13155                 /* impossible */
13156                 break;
13157             }
13158             break;
13159
13160           case PGNTag:
13161             switch (lastLoadGameStart) {
13162               case GNUChessGame:
13163               case PGNTag:
13164               case MoveNumberOne:
13165               case EndOfFile:
13166                 gn--;           /* count this game */
13167                 lastLoadGameStart = cm;
13168                 break;
13169               case XBoardGame:
13170                 lastLoadGameStart = cm; /* game counted already */
13171                 break;
13172               default:
13173                 /* impossible */
13174                 break;
13175             }
13176             if (gn > 0) {
13177                 do {
13178                     yyboardindex = forwardMostMove;
13179                     cm = (ChessMove) Myylex();
13180                 } while (cm == PGNTag || cm == Comment);
13181             }
13182             break;
13183
13184           case WhiteWins:
13185           case BlackWins:
13186           case GameIsDrawn:
13187             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13188                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13189                     != CMAIL_OLD_RESULT) {
13190                     nCmailResults ++ ;
13191                     cmailResult[  CMAIL_MAX_GAMES
13192                                 - gn - 1] = CMAIL_OLD_RESULT;
13193                 }
13194             }
13195             break;
13196
13197           case NormalMove:
13198           case FirstLeg:
13199             /* Only a NormalMove can be at the start of a game
13200              * without a position diagram. */
13201             if (lastLoadGameStart == EndOfFile ) {
13202               gn--;
13203               lastLoadGameStart = MoveNumberOne;
13204             }
13205             break;
13206
13207           default:
13208             break;
13209         }
13210     }
13211
13212     if (appData.debugMode)
13213       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13214
13215     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13216
13217     if (cm == XBoardGame) {
13218         /* Skip any header junk before position diagram and/or move 1 */
13219         for (;;) {
13220             yyboardindex = forwardMostMove;
13221             cm = (ChessMove) Myylex();
13222
13223             if (cm == EndOfFile ||
13224                 cm == GNUChessGame || cm == XBoardGame) {
13225                 /* Empty game; pretend end-of-file and handle later */
13226                 cm = EndOfFile;
13227                 break;
13228             }
13229
13230             if (cm == MoveNumberOne || cm == PositionDiagram ||
13231                 cm == PGNTag || cm == Comment)
13232               break;
13233         }
13234     } else if (cm == GNUChessGame) {
13235         if (gameInfo.event != NULL) {
13236             free(gameInfo.event);
13237         }
13238         gameInfo.event = StrSave(yy_text);
13239     }
13240
13241     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13242     while (cm == PGNTag) {
13243         if (appData.debugMode)
13244           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13245         err = ParsePGNTag(yy_text, &gameInfo);
13246         if (!err) numPGNTags++;
13247
13248         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13249         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13250             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13251             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13252             InitPosition(TRUE);
13253             oldVariant = gameInfo.variant;
13254             if (appData.debugMode)
13255               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13256         }
13257
13258
13259         if (gameInfo.fen != NULL) {
13260           Board initial_position;
13261           startedFromSetupPosition = TRUE;
13262           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13263             Reset(TRUE, TRUE);
13264             DisplayError(_("Bad FEN position in file"), 0);
13265             return FALSE;
13266           }
13267           CopyBoard(boards[0], initial_position);
13268           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13269             CopyBoard(initialPosition, initial_position);
13270           if (blackPlaysFirst) {
13271             currentMove = forwardMostMove = backwardMostMove = 1;
13272             CopyBoard(boards[1], initial_position);
13273             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13274             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13275             timeRemaining[0][1] = whiteTimeRemaining;
13276             timeRemaining[1][1] = blackTimeRemaining;
13277             if (commentList[0] != NULL) {
13278               commentList[1] = commentList[0];
13279               commentList[0] = NULL;
13280             }
13281           } else {
13282             currentMove = forwardMostMove = backwardMostMove = 0;
13283           }
13284           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13285           {   int i;
13286               initialRulePlies = FENrulePlies;
13287               for( i=0; i< nrCastlingRights; i++ )
13288                   initialRights[i] = initial_position[CASTLING][i];
13289           }
13290           yyboardindex = forwardMostMove;
13291           free(gameInfo.fen);
13292           gameInfo.fen = NULL;
13293         }
13294
13295         yyboardindex = forwardMostMove;
13296         cm = (ChessMove) Myylex();
13297
13298         /* Handle comments interspersed among the tags */
13299         while (cm == Comment) {
13300             char *p;
13301             if (appData.debugMode)
13302               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13303             p = yy_text;
13304             AppendComment(currentMove, p, FALSE);
13305             yyboardindex = forwardMostMove;
13306             cm = (ChessMove) Myylex();
13307         }
13308     }
13309
13310     /* don't rely on existence of Event tag since if game was
13311      * pasted from clipboard the Event tag may not exist
13312      */
13313     if (numPGNTags > 0){
13314         char *tags;
13315         if (gameInfo.variant == VariantNormal) {
13316           VariantClass v = StringToVariant(gameInfo.event);
13317           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13318           if(v < VariantShogi) gameInfo.variant = v;
13319         }
13320         if (!matchMode) {
13321           if( appData.autoDisplayTags ) {
13322             tags = PGNTags(&gameInfo);
13323             TagsPopUp(tags, CmailMsg());
13324             free(tags);
13325           }
13326         }
13327     } else {
13328         /* Make something up, but don't display it now */
13329         SetGameInfo();
13330         TagsPopDown();
13331     }
13332
13333     if (cm == PositionDiagram) {
13334         int i, j;
13335         char *p;
13336         Board initial_position;
13337
13338         if (appData.debugMode)
13339           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13340
13341         if (!startedFromSetupPosition) {
13342             p = yy_text;
13343             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13344               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13345                 switch (*p) {
13346                   case '{':
13347                   case '[':
13348                   case '-':
13349                   case ' ':
13350                   case '\t':
13351                   case '\n':
13352                   case '\r':
13353                     break;
13354                   default:
13355                     initial_position[i][j++] = CharToPiece(*p);
13356                     break;
13357                 }
13358             while (*p == ' ' || *p == '\t' ||
13359                    *p == '\n' || *p == '\r') p++;
13360
13361             if (strncmp(p, "black", strlen("black"))==0)
13362               blackPlaysFirst = TRUE;
13363             else
13364               blackPlaysFirst = FALSE;
13365             startedFromSetupPosition = TRUE;
13366
13367             CopyBoard(boards[0], initial_position);
13368             if (blackPlaysFirst) {
13369                 currentMove = forwardMostMove = backwardMostMove = 1;
13370                 CopyBoard(boards[1], initial_position);
13371                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13372                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13373                 timeRemaining[0][1] = whiteTimeRemaining;
13374                 timeRemaining[1][1] = blackTimeRemaining;
13375                 if (commentList[0] != NULL) {
13376                     commentList[1] = commentList[0];
13377                     commentList[0] = NULL;
13378                 }
13379             } else {
13380                 currentMove = forwardMostMove = backwardMostMove = 0;
13381             }
13382         }
13383         yyboardindex = forwardMostMove;
13384         cm = (ChessMove) Myylex();
13385     }
13386
13387   if(!creatingBook) {
13388     if (first.pr == NoProc) {
13389         StartChessProgram(&first);
13390     }
13391     InitChessProgram(&first, FALSE);
13392     if(gameInfo.variant == VariantUnknown && *oldName) {
13393         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13394         gameInfo.variant = v;
13395     }
13396     SendToProgram("force\n", &first);
13397     if (startedFromSetupPosition) {
13398         SendBoard(&first, forwardMostMove);
13399     if (appData.debugMode) {
13400         fprintf(debugFP, "Load Game\n");
13401     }
13402         DisplayBothClocks();
13403     }
13404   }
13405
13406     /* [HGM] server: flag to write setup moves in broadcast file as one */
13407     loadFlag = appData.suppressLoadMoves;
13408
13409     while (cm == Comment) {
13410         char *p;
13411         if (appData.debugMode)
13412           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13413         p = yy_text;
13414         AppendComment(currentMove, p, FALSE);
13415         yyboardindex = forwardMostMove;
13416         cm = (ChessMove) Myylex();
13417     }
13418
13419     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13420         cm == WhiteWins || cm == BlackWins ||
13421         cm == GameIsDrawn || cm == GameUnfinished) {
13422         DisplayMessage("", _("No moves in game"));
13423         if (cmailMsgLoaded) {
13424             if (appData.debugMode)
13425               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13426             ClearHighlights();
13427             flipView = FALSE;
13428         }
13429         DrawPosition(FALSE, boards[currentMove]);
13430         DisplayBothClocks();
13431         gameMode = EditGame;
13432         ModeHighlight();
13433         gameFileFP = NULL;
13434         cmailOldMove = 0;
13435         return TRUE;
13436     }
13437
13438     // [HGM] PV info: routine tests if comment empty
13439     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13440         DisplayComment(currentMove - 1, commentList[currentMove]);
13441     }
13442     if (!matchMode && appData.timeDelay != 0)
13443       DrawPosition(FALSE, boards[currentMove]);
13444
13445     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13446       programStats.ok_to_send = 1;
13447     }
13448
13449     /* if the first token after the PGN tags is a move
13450      * and not move number 1, retrieve it from the parser
13451      */
13452     if (cm != MoveNumberOne)
13453         LoadGameOneMove(cm);
13454
13455     /* load the remaining moves from the file */
13456     while (LoadGameOneMove(EndOfFile)) {
13457       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13458       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13459     }
13460
13461     /* rewind to the start of the game */
13462     currentMove = backwardMostMove;
13463
13464     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13465
13466     if (oldGameMode == AnalyzeFile) {
13467       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13468       AnalyzeFileEvent();
13469     } else
13470     if (oldGameMode == AnalyzeMode) {
13471       AnalyzeFileEvent();
13472     }
13473
13474     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13475         long int w, b; // [HGM] adjourn: restore saved clock times
13476         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13477         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13478             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13479             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13480         }
13481     }
13482
13483     if(creatingBook) return TRUE;
13484     if (!matchMode && pos > 0) {
13485         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13486     } else
13487     if (matchMode || appData.timeDelay == 0) {
13488       ToEndEvent();
13489     } else if (appData.timeDelay > 0) {
13490       AutoPlayGameLoop();
13491     }
13492
13493     if (appData.debugMode)
13494         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13495
13496     loadFlag = 0; /* [HGM] true game starts */
13497     return TRUE;
13498 }
13499
13500 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13501 int
13502 ReloadPosition (int offset)
13503 {
13504     int positionNumber = lastLoadPositionNumber + offset;
13505     if (lastLoadPositionFP == NULL) {
13506         DisplayError(_("No position has been loaded yet"), 0);
13507         return FALSE;
13508     }
13509     if (positionNumber <= 0) {
13510         DisplayError(_("Can't back up any further"), 0);
13511         return FALSE;
13512     }
13513     return LoadPosition(lastLoadPositionFP, positionNumber,
13514                         lastLoadPositionTitle);
13515 }
13516
13517 /* Load the nth position from the given file */
13518 int
13519 LoadPositionFromFile (char *filename, int n, char *title)
13520 {
13521     FILE *f;
13522     char buf[MSG_SIZ];
13523
13524     if (strcmp(filename, "-") == 0) {
13525         return LoadPosition(stdin, n, "stdin");
13526     } else {
13527         f = fopen(filename, "rb");
13528         if (f == NULL) {
13529             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13530             DisplayError(buf, errno);
13531             return FALSE;
13532         } else {
13533             return LoadPosition(f, n, title);
13534         }
13535     }
13536 }
13537
13538 /* Load the nth position from the given open file, and close it */
13539 int
13540 LoadPosition (FILE *f, int positionNumber, char *title)
13541 {
13542     char *p, line[MSG_SIZ];
13543     Board initial_position;
13544     int i, j, fenMode, pn;
13545
13546     if (gameMode == Training )
13547         SetTrainingModeOff();
13548
13549     if (gameMode != BeginningOfGame) {
13550         Reset(FALSE, TRUE);
13551     }
13552     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13553         fclose(lastLoadPositionFP);
13554     }
13555     if (positionNumber == 0) positionNumber = 1;
13556     lastLoadPositionFP = f;
13557     lastLoadPositionNumber = positionNumber;
13558     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13559     if (first.pr == NoProc && !appData.noChessProgram) {
13560       StartChessProgram(&first);
13561       InitChessProgram(&first, FALSE);
13562     }
13563     pn = positionNumber;
13564     if (positionNumber < 0) {
13565         /* Negative position number means to seek to that byte offset */
13566         if (fseek(f, -positionNumber, 0) == -1) {
13567             DisplayError(_("Can't seek on position file"), 0);
13568             return FALSE;
13569         };
13570         pn = 1;
13571     } else {
13572         if (fseek(f, 0, 0) == -1) {
13573             if (f == lastLoadPositionFP ?
13574                 positionNumber == lastLoadPositionNumber + 1 :
13575                 positionNumber == 1) {
13576                 pn = 1;
13577             } else {
13578                 DisplayError(_("Can't seek on position file"), 0);
13579                 return FALSE;
13580             }
13581         }
13582     }
13583     /* See if this file is FEN or old-style xboard */
13584     if (fgets(line, MSG_SIZ, f) == NULL) {
13585         DisplayError(_("Position not found in file"), 0);
13586         return FALSE;
13587     }
13588     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13589     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13590
13591     if (pn >= 2) {
13592         if (fenMode || line[0] == '#') pn--;
13593         while (pn > 0) {
13594             /* skip positions before number pn */
13595             if (fgets(line, MSG_SIZ, f) == NULL) {
13596                 Reset(TRUE, TRUE);
13597                 DisplayError(_("Position not found in file"), 0);
13598                 return FALSE;
13599             }
13600             if (fenMode || line[0] == '#') pn--;
13601         }
13602     }
13603
13604     if (fenMode) {
13605         char *p;
13606         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13607             DisplayError(_("Bad FEN position in file"), 0);
13608             return FALSE;
13609         }
13610         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13611             sscanf(p+4, "%[^;]", bestMove);
13612         } else *bestMove = NULLCHAR;
13613         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13614             sscanf(p+4, "%[^;]", avoidMove);
13615         } else *avoidMove = NULLCHAR;
13616     } else {
13617         (void) fgets(line, MSG_SIZ, f);
13618         (void) fgets(line, MSG_SIZ, f);
13619
13620         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13621             (void) fgets(line, MSG_SIZ, f);
13622             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13623                 if (*p == ' ')
13624                   continue;
13625                 initial_position[i][j++] = CharToPiece(*p);
13626             }
13627         }
13628
13629         blackPlaysFirst = FALSE;
13630         if (!feof(f)) {
13631             (void) fgets(line, MSG_SIZ, f);
13632             if (strncmp(line, "black", strlen("black"))==0)
13633               blackPlaysFirst = TRUE;
13634         }
13635     }
13636     startedFromSetupPosition = TRUE;
13637
13638     CopyBoard(boards[0], initial_position);
13639     if (blackPlaysFirst) {
13640         currentMove = forwardMostMove = backwardMostMove = 1;
13641         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13642         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13643         CopyBoard(boards[1], initial_position);
13644         DisplayMessage("", _("Black to play"));
13645     } else {
13646         currentMove = forwardMostMove = backwardMostMove = 0;
13647         DisplayMessage("", _("White to play"));
13648     }
13649     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13650     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13651         SendToProgram("force\n", &first);
13652         SendBoard(&first, forwardMostMove);
13653     }
13654     if (appData.debugMode) {
13655 int i, j;
13656   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13657   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13658         fprintf(debugFP, "Load Position\n");
13659     }
13660
13661     if (positionNumber > 1) {
13662       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13663         DisplayTitle(line);
13664     } else {
13665         DisplayTitle(title);
13666     }
13667     gameMode = EditGame;
13668     ModeHighlight();
13669     ResetClocks();
13670     timeRemaining[0][1] = whiteTimeRemaining;
13671     timeRemaining[1][1] = blackTimeRemaining;
13672     DrawPosition(FALSE, boards[currentMove]);
13673
13674     return TRUE;
13675 }
13676
13677
13678 void
13679 CopyPlayerNameIntoFileName (char **dest, char *src)
13680 {
13681     while (*src != NULLCHAR && *src != ',') {
13682         if (*src == ' ') {
13683             *(*dest)++ = '_';
13684             src++;
13685         } else {
13686             *(*dest)++ = *src++;
13687         }
13688     }
13689 }
13690
13691 char *
13692 DefaultFileName (char *ext)
13693 {
13694     static char def[MSG_SIZ];
13695     char *p;
13696
13697     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13698         p = def;
13699         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13700         *p++ = '-';
13701         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13702         *p++ = '.';
13703         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13704     } else {
13705         def[0] = NULLCHAR;
13706     }
13707     return def;
13708 }
13709
13710 /* Save the current game to the given file */
13711 int
13712 SaveGameToFile (char *filename, int append)
13713 {
13714     FILE *f;
13715     char buf[MSG_SIZ];
13716     int result, i, t,tot=0;
13717
13718     if (strcmp(filename, "-") == 0) {
13719         return SaveGame(stdout, 0, NULL);
13720     } else {
13721         for(i=0; i<10; i++) { // upto 10 tries
13722              f = fopen(filename, append ? "a" : "w");
13723              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13724              if(f || errno != 13) break;
13725              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13726              tot += t;
13727         }
13728         if (f == NULL) {
13729             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13730             DisplayError(buf, errno);
13731             return FALSE;
13732         } else {
13733             safeStrCpy(buf, lastMsg, MSG_SIZ);
13734             DisplayMessage(_("Waiting for access to save file"), "");
13735             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13736             DisplayMessage(_("Saving game"), "");
13737             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13738             result = SaveGame(f, 0, NULL);
13739             DisplayMessage(buf, "");
13740             return result;
13741         }
13742     }
13743 }
13744
13745 char *
13746 SavePart (char *str)
13747 {
13748     static char buf[MSG_SIZ];
13749     char *p;
13750
13751     p = strchr(str, ' ');
13752     if (p == NULL) return str;
13753     strncpy(buf, str, p - str);
13754     buf[p - str] = NULLCHAR;
13755     return buf;
13756 }
13757
13758 #define PGN_MAX_LINE 75
13759
13760 #define PGN_SIDE_WHITE  0
13761 #define PGN_SIDE_BLACK  1
13762
13763 static int
13764 FindFirstMoveOutOfBook (int side)
13765 {
13766     int result = -1;
13767
13768     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13769         int index = backwardMostMove;
13770         int has_book_hit = 0;
13771
13772         if( (index % 2) != side ) {
13773             index++;
13774         }
13775
13776         while( index < forwardMostMove ) {
13777             /* Check to see if engine is in book */
13778             int depth = pvInfoList[index].depth;
13779             int score = pvInfoList[index].score;
13780             int in_book = 0;
13781
13782             if( depth <= 2 ) {
13783                 in_book = 1;
13784             }
13785             else if( score == 0 && depth == 63 ) {
13786                 in_book = 1; /* Zappa */
13787             }
13788             else if( score == 2 && depth == 99 ) {
13789                 in_book = 1; /* Abrok */
13790             }
13791
13792             has_book_hit += in_book;
13793
13794             if( ! in_book ) {
13795                 result = index;
13796
13797                 break;
13798             }
13799
13800             index += 2;
13801         }
13802     }
13803
13804     return result;
13805 }
13806
13807 void
13808 GetOutOfBookInfo (char * buf)
13809 {
13810     int oob[2];
13811     int i;
13812     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13813
13814     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13815     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13816
13817     *buf = '\0';
13818
13819     if( oob[0] >= 0 || oob[1] >= 0 ) {
13820         for( i=0; i<2; i++ ) {
13821             int idx = oob[i];
13822
13823             if( idx >= 0 ) {
13824                 if( i > 0 && oob[0] >= 0 ) {
13825                     strcat( buf, "   " );
13826                 }
13827
13828                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13829                 sprintf( buf+strlen(buf), "%s%.2f",
13830                     pvInfoList[idx].score >= 0 ? "+" : "",
13831                     pvInfoList[idx].score / 100.0 );
13832             }
13833         }
13834     }
13835 }
13836
13837 /* Save game in PGN style */
13838 static void
13839 SaveGamePGN2 (FILE *f)
13840 {
13841     int i, offset, linelen, newblock;
13842 //    char *movetext;
13843     char numtext[32];
13844     int movelen, numlen, blank;
13845     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13846
13847     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13848
13849     PrintPGNTags(f, &gameInfo);
13850
13851     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13852
13853     if (backwardMostMove > 0 || startedFromSetupPosition) {
13854         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13855         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13856         fprintf(f, "\n{--------------\n");
13857         PrintPosition(f, backwardMostMove);
13858         fprintf(f, "--------------}\n");
13859         free(fen);
13860     }
13861     else {
13862         /* [AS] Out of book annotation */
13863         if( appData.saveOutOfBookInfo ) {
13864             char buf[64];
13865
13866             GetOutOfBookInfo( buf );
13867
13868             if( buf[0] != '\0' ) {
13869                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13870             }
13871         }
13872
13873         fprintf(f, "\n");
13874     }
13875
13876     i = backwardMostMove;
13877     linelen = 0;
13878     newblock = TRUE;
13879
13880     while (i < forwardMostMove) {
13881         /* Print comments preceding this move */
13882         if (commentList[i] != NULL) {
13883             if (linelen > 0) fprintf(f, "\n");
13884             fprintf(f, "%s", commentList[i]);
13885             linelen = 0;
13886             newblock = TRUE;
13887         }
13888
13889         /* Format move number */
13890         if ((i % 2) == 0)
13891           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13892         else
13893           if (newblock)
13894             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13895           else
13896             numtext[0] = NULLCHAR;
13897
13898         numlen = strlen(numtext);
13899         newblock = FALSE;
13900
13901         /* Print move number */
13902         blank = linelen > 0 && numlen > 0;
13903         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13904             fprintf(f, "\n");
13905             linelen = 0;
13906             blank = 0;
13907         }
13908         if (blank) {
13909             fprintf(f, " ");
13910             linelen++;
13911         }
13912         fprintf(f, "%s", numtext);
13913         linelen += numlen;
13914
13915         /* Get move */
13916         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13917         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13918
13919         /* Print move */
13920         blank = linelen > 0 && movelen > 0;
13921         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13922             fprintf(f, "\n");
13923             linelen = 0;
13924             blank = 0;
13925         }
13926         if (blank) {
13927             fprintf(f, " ");
13928             linelen++;
13929         }
13930         fprintf(f, "%s", move_buffer);
13931         linelen += movelen;
13932
13933         /* [AS] Add PV info if present */
13934         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13935             /* [HGM] add time */
13936             char buf[MSG_SIZ]; int seconds;
13937
13938             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13939
13940             if( seconds <= 0)
13941               buf[0] = 0;
13942             else
13943               if( seconds < 30 )
13944                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13945               else
13946                 {
13947                   seconds = (seconds + 4)/10; // round to full seconds
13948                   if( seconds < 60 )
13949                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13950                   else
13951                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13952                 }
13953
13954             if(appData.cumulativeTimePGN) {
13955                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13956             }
13957
13958             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13959                       pvInfoList[i].score >= 0 ? "+" : "",
13960                       pvInfoList[i].score / 100.0,
13961                       pvInfoList[i].depth,
13962                       buf );
13963
13964             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13965
13966             /* Print score/depth */
13967             blank = linelen > 0 && movelen > 0;
13968             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13969                 fprintf(f, "\n");
13970                 linelen = 0;
13971                 blank = 0;
13972             }
13973             if (blank) {
13974                 fprintf(f, " ");
13975                 linelen++;
13976             }
13977             fprintf(f, "%s", move_buffer);
13978             linelen += movelen;
13979         }
13980
13981         i++;
13982     }
13983
13984     /* Start a new line */
13985     if (linelen > 0) fprintf(f, "\n");
13986
13987     /* Print comments after last move */
13988     if (commentList[i] != NULL) {
13989         fprintf(f, "%s\n", commentList[i]);
13990     }
13991
13992     /* Print result */
13993     if (gameInfo.resultDetails != NULL &&
13994         gameInfo.resultDetails[0] != NULLCHAR) {
13995         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13996         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13997            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13998             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13999         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14000     } else {
14001         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14002     }
14003 }
14004
14005 /* Save game in PGN style and close the file */
14006 int
14007 SaveGamePGN (FILE *f)
14008 {
14009     SaveGamePGN2(f);
14010     fclose(f);
14011     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14012     return TRUE;
14013 }
14014
14015 /* Save game in old style and close the file */
14016 int
14017 SaveGameOldStyle (FILE *f)
14018 {
14019     int i, offset;
14020     time_t tm;
14021
14022     tm = time((time_t *) NULL);
14023
14024     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14025     PrintOpponents(f);
14026
14027     if (backwardMostMove > 0 || startedFromSetupPosition) {
14028         fprintf(f, "\n[--------------\n");
14029         PrintPosition(f, backwardMostMove);
14030         fprintf(f, "--------------]\n");
14031     } else {
14032         fprintf(f, "\n");
14033     }
14034
14035     i = backwardMostMove;
14036     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14037
14038     while (i < forwardMostMove) {
14039         if (commentList[i] != NULL) {
14040             fprintf(f, "[%s]\n", commentList[i]);
14041         }
14042
14043         if ((i % 2) == 1) {
14044             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14045             i++;
14046         } else {
14047             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14048             i++;
14049             if (commentList[i] != NULL) {
14050                 fprintf(f, "\n");
14051                 continue;
14052             }
14053             if (i >= forwardMostMove) {
14054                 fprintf(f, "\n");
14055                 break;
14056             }
14057             fprintf(f, "%s\n", parseList[i]);
14058             i++;
14059         }
14060     }
14061
14062     if (commentList[i] != NULL) {
14063         fprintf(f, "[%s]\n", commentList[i]);
14064     }
14065
14066     /* This isn't really the old style, but it's close enough */
14067     if (gameInfo.resultDetails != NULL &&
14068         gameInfo.resultDetails[0] != NULLCHAR) {
14069         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14070                 gameInfo.resultDetails);
14071     } else {
14072         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14073     }
14074
14075     fclose(f);
14076     return TRUE;
14077 }
14078
14079 /* Save the current game to open file f and close the file */
14080 int
14081 SaveGame (FILE *f, int dummy, char *dummy2)
14082 {
14083     if (gameMode == EditPosition) EditPositionDone(TRUE);
14084     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14085     if (appData.oldSaveStyle)
14086       return SaveGameOldStyle(f);
14087     else
14088       return SaveGamePGN(f);
14089 }
14090
14091 /* Save the current position to the given file */
14092 int
14093 SavePositionToFile (char *filename)
14094 {
14095     FILE *f;
14096     char buf[MSG_SIZ];
14097
14098     if (strcmp(filename, "-") == 0) {
14099         return SavePosition(stdout, 0, NULL);
14100     } else {
14101         f = fopen(filename, "a");
14102         if (f == NULL) {
14103             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14104             DisplayError(buf, errno);
14105             return FALSE;
14106         } else {
14107             safeStrCpy(buf, lastMsg, MSG_SIZ);
14108             DisplayMessage(_("Waiting for access to save file"), "");
14109             flock(fileno(f), LOCK_EX); // [HGM] lock
14110             DisplayMessage(_("Saving position"), "");
14111             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14112             SavePosition(f, 0, NULL);
14113             DisplayMessage(buf, "");
14114             return TRUE;
14115         }
14116     }
14117 }
14118
14119 /* Save the current position to the given open file and close the file */
14120 int
14121 SavePosition (FILE *f, int dummy, char *dummy2)
14122 {
14123     time_t tm;
14124     char *fen;
14125
14126     if (gameMode == EditPosition) EditPositionDone(TRUE);
14127     if (appData.oldSaveStyle) {
14128         tm = time((time_t *) NULL);
14129
14130         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14131         PrintOpponents(f);
14132         fprintf(f, "[--------------\n");
14133         PrintPosition(f, currentMove);
14134         fprintf(f, "--------------]\n");
14135     } else {
14136         fen = PositionToFEN(currentMove, NULL, 1);
14137         fprintf(f, "%s\n", fen);
14138         free(fen);
14139     }
14140     fclose(f);
14141     return TRUE;
14142 }
14143
14144 void
14145 ReloadCmailMsgEvent (int unregister)
14146 {
14147 #if !WIN32
14148     static char *inFilename = NULL;
14149     static char *outFilename;
14150     int i;
14151     struct stat inbuf, outbuf;
14152     int status;
14153
14154     /* Any registered moves are unregistered if unregister is set, */
14155     /* i.e. invoked by the signal handler */
14156     if (unregister) {
14157         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14158             cmailMoveRegistered[i] = FALSE;
14159             if (cmailCommentList[i] != NULL) {
14160                 free(cmailCommentList[i]);
14161                 cmailCommentList[i] = NULL;
14162             }
14163         }
14164         nCmailMovesRegistered = 0;
14165     }
14166
14167     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14168         cmailResult[i] = CMAIL_NOT_RESULT;
14169     }
14170     nCmailResults = 0;
14171
14172     if (inFilename == NULL) {
14173         /* Because the filenames are static they only get malloced once  */
14174         /* and they never get freed                                      */
14175         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14176         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14177
14178         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14179         sprintf(outFilename, "%s.out", appData.cmailGameName);
14180     }
14181
14182     status = stat(outFilename, &outbuf);
14183     if (status < 0) {
14184         cmailMailedMove = FALSE;
14185     } else {
14186         status = stat(inFilename, &inbuf);
14187         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14188     }
14189
14190     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14191        counts the games, notes how each one terminated, etc.
14192
14193        It would be nice to remove this kludge and instead gather all
14194        the information while building the game list.  (And to keep it
14195        in the game list nodes instead of having a bunch of fixed-size
14196        parallel arrays.)  Note this will require getting each game's
14197        termination from the PGN tags, as the game list builder does
14198        not process the game moves.  --mann
14199        */
14200     cmailMsgLoaded = TRUE;
14201     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14202
14203     /* Load first game in the file or popup game menu */
14204     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14205
14206 #endif /* !WIN32 */
14207     return;
14208 }
14209
14210 int
14211 RegisterMove ()
14212 {
14213     FILE *f;
14214     char string[MSG_SIZ];
14215
14216     if (   cmailMailedMove
14217         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14218         return TRUE;            /* Allow free viewing  */
14219     }
14220
14221     /* Unregister move to ensure that we don't leave RegisterMove        */
14222     /* with the move registered when the conditions for registering no   */
14223     /* longer hold                                                       */
14224     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14225         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14226         nCmailMovesRegistered --;
14227
14228         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14229           {
14230               free(cmailCommentList[lastLoadGameNumber - 1]);
14231               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14232           }
14233     }
14234
14235     if (cmailOldMove == -1) {
14236         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14237         return FALSE;
14238     }
14239
14240     if (currentMove > cmailOldMove + 1) {
14241         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14242         return FALSE;
14243     }
14244
14245     if (currentMove < cmailOldMove) {
14246         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14247         return FALSE;
14248     }
14249
14250     if (forwardMostMove > currentMove) {
14251         /* Silently truncate extra moves */
14252         TruncateGame();
14253     }
14254
14255     if (   (currentMove == cmailOldMove + 1)
14256         || (   (currentMove == cmailOldMove)
14257             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14258                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14259         if (gameInfo.result != GameUnfinished) {
14260             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14261         }
14262
14263         if (commentList[currentMove] != NULL) {
14264             cmailCommentList[lastLoadGameNumber - 1]
14265               = StrSave(commentList[currentMove]);
14266         }
14267         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14268
14269         if (appData.debugMode)
14270           fprintf(debugFP, "Saving %s for game %d\n",
14271                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14272
14273         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14274
14275         f = fopen(string, "w");
14276         if (appData.oldSaveStyle) {
14277             SaveGameOldStyle(f); /* also closes the file */
14278
14279             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14280             f = fopen(string, "w");
14281             SavePosition(f, 0, NULL); /* also closes the file */
14282         } else {
14283             fprintf(f, "{--------------\n");
14284             PrintPosition(f, currentMove);
14285             fprintf(f, "--------------}\n\n");
14286
14287             SaveGame(f, 0, NULL); /* also closes the file*/
14288         }
14289
14290         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14291         nCmailMovesRegistered ++;
14292     } else if (nCmailGames == 1) {
14293         DisplayError(_("You have not made a move yet"), 0);
14294         return FALSE;
14295     }
14296
14297     return TRUE;
14298 }
14299
14300 void
14301 MailMoveEvent ()
14302 {
14303 #if !WIN32
14304     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14305     FILE *commandOutput;
14306     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14307     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14308     int nBuffers;
14309     int i;
14310     int archived;
14311     char *arcDir;
14312
14313     if (! cmailMsgLoaded) {
14314         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14315         return;
14316     }
14317
14318     if (nCmailGames == nCmailResults) {
14319         DisplayError(_("No unfinished games"), 0);
14320         return;
14321     }
14322
14323 #if CMAIL_PROHIBIT_REMAIL
14324     if (cmailMailedMove) {
14325       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);
14326         DisplayError(msg, 0);
14327         return;
14328     }
14329 #endif
14330
14331     if (! (cmailMailedMove || RegisterMove())) return;
14332
14333     if (   cmailMailedMove
14334         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14335       snprintf(string, MSG_SIZ, partCommandString,
14336                appData.debugMode ? " -v" : "", appData.cmailGameName);
14337         commandOutput = popen(string, "r");
14338
14339         if (commandOutput == NULL) {
14340             DisplayError(_("Failed to invoke cmail"), 0);
14341         } else {
14342             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14343                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14344             }
14345             if (nBuffers > 1) {
14346                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14347                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14348                 nBytes = MSG_SIZ - 1;
14349             } else {
14350                 (void) memcpy(msg, buffer, nBytes);
14351             }
14352             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14353
14354             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14355                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14356
14357                 archived = TRUE;
14358                 for (i = 0; i < nCmailGames; i ++) {
14359                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14360                         archived = FALSE;
14361                     }
14362                 }
14363                 if (   archived
14364                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14365                         != NULL)) {
14366                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14367                            arcDir,
14368                            appData.cmailGameName,
14369                            gameInfo.date);
14370                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14371                     cmailMsgLoaded = FALSE;
14372                 }
14373             }
14374
14375             DisplayInformation(msg);
14376             pclose(commandOutput);
14377         }
14378     } else {
14379         if ((*cmailMsg) != '\0') {
14380             DisplayInformation(cmailMsg);
14381         }
14382     }
14383
14384     return;
14385 #endif /* !WIN32 */
14386 }
14387
14388 char *
14389 CmailMsg ()
14390 {
14391 #if WIN32
14392     return NULL;
14393 #else
14394     int  prependComma = 0;
14395     char number[5];
14396     char string[MSG_SIZ];       /* Space for game-list */
14397     int  i;
14398
14399     if (!cmailMsgLoaded) return "";
14400
14401     if (cmailMailedMove) {
14402       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14403     } else {
14404         /* Create a list of games left */
14405       snprintf(string, MSG_SIZ, "[");
14406         for (i = 0; i < nCmailGames; i ++) {
14407             if (! (   cmailMoveRegistered[i]
14408                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14409                 if (prependComma) {
14410                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14411                 } else {
14412                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14413                     prependComma = 1;
14414                 }
14415
14416                 strcat(string, number);
14417             }
14418         }
14419         strcat(string, "]");
14420
14421         if (nCmailMovesRegistered + nCmailResults == 0) {
14422             switch (nCmailGames) {
14423               case 1:
14424                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14425                 break;
14426
14427               case 2:
14428                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14429                 break;
14430
14431               default:
14432                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14433                          nCmailGames);
14434                 break;
14435             }
14436         } else {
14437             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14438               case 1:
14439                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14440                          string);
14441                 break;
14442
14443               case 0:
14444                 if (nCmailResults == nCmailGames) {
14445                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14446                 } else {
14447                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14448                 }
14449                 break;
14450
14451               default:
14452                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14453                          string);
14454             }
14455         }
14456     }
14457     return cmailMsg;
14458 #endif /* WIN32 */
14459 }
14460
14461 void
14462 ResetGameEvent ()
14463 {
14464     if (gameMode == Training)
14465       SetTrainingModeOff();
14466
14467     Reset(TRUE, TRUE);
14468     cmailMsgLoaded = FALSE;
14469     if (appData.icsActive) {
14470       SendToICS(ics_prefix);
14471       SendToICS("refresh\n");
14472     }
14473 }
14474
14475 void
14476 ExitEvent (int status)
14477 {
14478     exiting++;
14479     if (exiting > 2) {
14480       /* Give up on clean exit */
14481       exit(status);
14482     }
14483     if (exiting > 1) {
14484       /* Keep trying for clean exit */
14485       return;
14486     }
14487
14488     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14489     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14490
14491     if (telnetISR != NULL) {
14492       RemoveInputSource(telnetISR);
14493     }
14494     if (icsPR != NoProc) {
14495       DestroyChildProcess(icsPR, TRUE);
14496     }
14497
14498     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14499     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14500
14501     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14502     /* make sure this other one finishes before killing it!                  */
14503     if(endingGame) { int count = 0;
14504         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14505         while(endingGame && count++ < 10) DoSleep(1);
14506         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14507     }
14508
14509     /* Kill off chess programs */
14510     if (first.pr != NoProc) {
14511         ExitAnalyzeMode();
14512
14513         DoSleep( appData.delayBeforeQuit );
14514         SendToProgram("quit\n", &first);
14515         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14516     }
14517     if (second.pr != NoProc) {
14518         DoSleep( appData.delayBeforeQuit );
14519         SendToProgram("quit\n", &second);
14520         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14521     }
14522     if (first.isr != NULL) {
14523         RemoveInputSource(first.isr);
14524     }
14525     if (second.isr != NULL) {
14526         RemoveInputSource(second.isr);
14527     }
14528
14529     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14530     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14531
14532     ShutDownFrontEnd();
14533     exit(status);
14534 }
14535
14536 void
14537 PauseEngine (ChessProgramState *cps)
14538 {
14539     SendToProgram("pause\n", cps);
14540     cps->pause = 2;
14541 }
14542
14543 void
14544 UnPauseEngine (ChessProgramState *cps)
14545 {
14546     SendToProgram("resume\n", cps);
14547     cps->pause = 1;
14548 }
14549
14550 void
14551 PauseEvent ()
14552 {
14553     if (appData.debugMode)
14554         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14555     if (pausing) {
14556         pausing = FALSE;
14557         ModeHighlight();
14558         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14559             StartClocks();
14560             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14561                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14562                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14563             }
14564             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14565             HandleMachineMove(stashedInputMove, stalledEngine);
14566             stalledEngine = NULL;
14567             return;
14568         }
14569         if (gameMode == MachinePlaysWhite ||
14570             gameMode == TwoMachinesPlay   ||
14571             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14572             if(first.pause)  UnPauseEngine(&first);
14573             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14574             if(second.pause) UnPauseEngine(&second);
14575             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14576             StartClocks();
14577         } else {
14578             DisplayBothClocks();
14579         }
14580         if (gameMode == PlayFromGameFile) {
14581             if (appData.timeDelay >= 0)
14582                 AutoPlayGameLoop();
14583         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14584             Reset(FALSE, TRUE);
14585             SendToICS(ics_prefix);
14586             SendToICS("refresh\n");
14587         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14588             ForwardInner(forwardMostMove);
14589         }
14590         pauseExamInvalid = FALSE;
14591     } else {
14592         switch (gameMode) {
14593           default:
14594             return;
14595           case IcsExamining:
14596             pauseExamForwardMostMove = forwardMostMove;
14597             pauseExamInvalid = FALSE;
14598             /* fall through */
14599           case IcsObserving:
14600           case IcsPlayingWhite:
14601           case IcsPlayingBlack:
14602             pausing = TRUE;
14603             ModeHighlight();
14604             return;
14605           case PlayFromGameFile:
14606             (void) StopLoadGameTimer();
14607             pausing = TRUE;
14608             ModeHighlight();
14609             break;
14610           case BeginningOfGame:
14611             if (appData.icsActive) return;
14612             /* else fall through */
14613           case MachinePlaysWhite:
14614           case MachinePlaysBlack:
14615           case TwoMachinesPlay:
14616             if (forwardMostMove == 0)
14617               return;           /* don't pause if no one has moved */
14618             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14619                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14620                 if(onMove->pause) {           // thinking engine can be paused
14621                     PauseEngine(onMove);      // do it
14622                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14623                         PauseEngine(onMove->other);
14624                     else
14625                         SendToProgram("easy\n", onMove->other);
14626                     StopClocks();
14627                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14628             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14629                 if(first.pause) {
14630                     PauseEngine(&first);
14631                     StopClocks();
14632                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14633             } else { // human on move, pause pondering by either method
14634                 if(first.pause)
14635                     PauseEngine(&first);
14636                 else if(appData.ponderNextMove)
14637                     SendToProgram("easy\n", &first);
14638                 StopClocks();
14639             }
14640             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14641           case AnalyzeMode:
14642             pausing = TRUE;
14643             ModeHighlight();
14644             break;
14645         }
14646     }
14647 }
14648
14649 void
14650 EditCommentEvent ()
14651 {
14652     char title[MSG_SIZ];
14653
14654     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14655       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14656     } else {
14657       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14658                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14659                parseList[currentMove - 1]);
14660     }
14661
14662     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14663 }
14664
14665
14666 void
14667 EditTagsEvent ()
14668 {
14669     char *tags = PGNTags(&gameInfo);
14670     bookUp = FALSE;
14671     EditTagsPopUp(tags, NULL);
14672     free(tags);
14673 }
14674
14675 void
14676 ToggleSecond ()
14677 {
14678   if(second.analyzing) {
14679     SendToProgram("exit\n", &second);
14680     second.analyzing = FALSE;
14681   } else {
14682     if (second.pr == NoProc) StartChessProgram(&second);
14683     InitChessProgram(&second, FALSE);
14684     FeedMovesToProgram(&second, currentMove);
14685
14686     SendToProgram("analyze\n", &second);
14687     second.analyzing = TRUE;
14688   }
14689 }
14690
14691 /* Toggle ShowThinking */
14692 void
14693 ToggleShowThinking()
14694 {
14695   appData.showThinking = !appData.showThinking;
14696   ShowThinkingEvent();
14697 }
14698
14699 int
14700 AnalyzeModeEvent ()
14701 {
14702     char buf[MSG_SIZ];
14703
14704     if (!first.analysisSupport) {
14705       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14706       DisplayError(buf, 0);
14707       return 0;
14708     }
14709     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14710     if (appData.icsActive) {
14711         if (gameMode != IcsObserving) {
14712           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14713             DisplayError(buf, 0);
14714             /* secure check */
14715             if (appData.icsEngineAnalyze) {
14716                 if (appData.debugMode)
14717                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14718                 ExitAnalyzeMode();
14719                 ModeHighlight();
14720             }
14721             return 0;
14722         }
14723         /* if enable, user wants to disable icsEngineAnalyze */
14724         if (appData.icsEngineAnalyze) {
14725                 ExitAnalyzeMode();
14726                 ModeHighlight();
14727                 return 0;
14728         }
14729         appData.icsEngineAnalyze = TRUE;
14730         if (appData.debugMode)
14731             fprintf(debugFP, "ICS engine analyze starting... \n");
14732     }
14733
14734     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14735     if (appData.noChessProgram || gameMode == AnalyzeMode)
14736       return 0;
14737
14738     if (gameMode != AnalyzeFile) {
14739         if (!appData.icsEngineAnalyze) {
14740                EditGameEvent();
14741                if (gameMode != EditGame) return 0;
14742         }
14743         if (!appData.showThinking) ToggleShowThinking();
14744         ResurrectChessProgram();
14745         SendToProgram("analyze\n", &first);
14746         first.analyzing = TRUE;
14747         /*first.maybeThinking = TRUE;*/
14748         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14749         EngineOutputPopUp();
14750     }
14751     if (!appData.icsEngineAnalyze) {
14752         gameMode = AnalyzeMode;
14753         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14754     }
14755     pausing = FALSE;
14756     ModeHighlight();
14757     SetGameInfo();
14758
14759     StartAnalysisClock();
14760     GetTimeMark(&lastNodeCountTime);
14761     lastNodeCount = 0;
14762     return 1;
14763 }
14764
14765 void
14766 AnalyzeFileEvent ()
14767 {
14768     if (appData.noChessProgram || gameMode == AnalyzeFile)
14769       return;
14770
14771     if (!first.analysisSupport) {
14772       char buf[MSG_SIZ];
14773       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14774       DisplayError(buf, 0);
14775       return;
14776     }
14777
14778     if (gameMode != AnalyzeMode) {
14779         keepInfo = 1; // mere annotating should not alter PGN tags
14780         EditGameEvent();
14781         keepInfo = 0;
14782         if (gameMode != EditGame) return;
14783         if (!appData.showThinking) ToggleShowThinking();
14784         ResurrectChessProgram();
14785         SendToProgram("analyze\n", &first);
14786         first.analyzing = TRUE;
14787         /*first.maybeThinking = TRUE;*/
14788         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14789         EngineOutputPopUp();
14790     }
14791     gameMode = AnalyzeFile;
14792     pausing = FALSE;
14793     ModeHighlight();
14794
14795     StartAnalysisClock();
14796     GetTimeMark(&lastNodeCountTime);
14797     lastNodeCount = 0;
14798     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14799     AnalysisPeriodicEvent(1);
14800 }
14801
14802 void
14803 MachineWhiteEvent ()
14804 {
14805     char buf[MSG_SIZ];
14806     char *bookHit = NULL;
14807
14808     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14809       return;
14810
14811
14812     if (gameMode == PlayFromGameFile ||
14813         gameMode == TwoMachinesPlay  ||
14814         gameMode == Training         ||
14815         gameMode == AnalyzeMode      ||
14816         gameMode == EndOfGame)
14817         EditGameEvent();
14818
14819     if (gameMode == EditPosition)
14820         EditPositionDone(TRUE);
14821
14822     if (!WhiteOnMove(currentMove)) {
14823         DisplayError(_("It is not White's turn"), 0);
14824         return;
14825     }
14826
14827     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14828       ExitAnalyzeMode();
14829
14830     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14831         gameMode == AnalyzeFile)
14832         TruncateGame();
14833
14834     ResurrectChessProgram();    /* in case it isn't running */
14835     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14836         gameMode = MachinePlaysWhite;
14837         ResetClocks();
14838     } else
14839     gameMode = MachinePlaysWhite;
14840     pausing = FALSE;
14841     ModeHighlight();
14842     SetGameInfo();
14843     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14844     DisplayTitle(buf);
14845     if (first.sendName) {
14846       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14847       SendToProgram(buf, &first);
14848     }
14849     if (first.sendTime) {
14850       if (first.useColors) {
14851         SendToProgram("black\n", &first); /*gnu kludge*/
14852       }
14853       SendTimeRemaining(&first, TRUE);
14854     }
14855     if (first.useColors) {
14856       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14857     }
14858     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14859     SetMachineThinkingEnables();
14860     first.maybeThinking = TRUE;
14861     StartClocks();
14862     firstMove = FALSE;
14863
14864     if (appData.autoFlipView && !flipView) {
14865       flipView = !flipView;
14866       DrawPosition(FALSE, NULL);
14867       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14868     }
14869
14870     if(bookHit) { // [HGM] book: simulate book reply
14871         static char bookMove[MSG_SIZ]; // a bit generous?
14872
14873         programStats.nodes = programStats.depth = programStats.time =
14874         programStats.score = programStats.got_only_move = 0;
14875         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14876
14877         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14878         strcat(bookMove, bookHit);
14879         HandleMachineMove(bookMove, &first);
14880     }
14881 }
14882
14883 void
14884 MachineBlackEvent ()
14885 {
14886   char buf[MSG_SIZ];
14887   char *bookHit = NULL;
14888
14889     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14890         return;
14891
14892
14893     if (gameMode == PlayFromGameFile ||
14894         gameMode == TwoMachinesPlay  ||
14895         gameMode == Training         ||
14896         gameMode == AnalyzeMode      ||
14897         gameMode == EndOfGame)
14898         EditGameEvent();
14899
14900     if (gameMode == EditPosition)
14901         EditPositionDone(TRUE);
14902
14903     if (WhiteOnMove(currentMove)) {
14904         DisplayError(_("It is not Black's turn"), 0);
14905         return;
14906     }
14907
14908     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14909       ExitAnalyzeMode();
14910
14911     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14912         gameMode == AnalyzeFile)
14913         TruncateGame();
14914
14915     ResurrectChessProgram();    /* in case it isn't running */
14916     gameMode = MachinePlaysBlack;
14917     pausing = FALSE;
14918     ModeHighlight();
14919     SetGameInfo();
14920     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14921     DisplayTitle(buf);
14922     if (first.sendName) {
14923       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14924       SendToProgram(buf, &first);
14925     }
14926     if (first.sendTime) {
14927       if (first.useColors) {
14928         SendToProgram("white\n", &first); /*gnu kludge*/
14929       }
14930       SendTimeRemaining(&first, FALSE);
14931     }
14932     if (first.useColors) {
14933       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14934     }
14935     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14936     SetMachineThinkingEnables();
14937     first.maybeThinking = TRUE;
14938     StartClocks();
14939
14940     if (appData.autoFlipView && flipView) {
14941       flipView = !flipView;
14942       DrawPosition(FALSE, NULL);
14943       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14944     }
14945     if(bookHit) { // [HGM] book: simulate book reply
14946         static char bookMove[MSG_SIZ]; // a bit generous?
14947
14948         programStats.nodes = programStats.depth = programStats.time =
14949         programStats.score = programStats.got_only_move = 0;
14950         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14951
14952         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14953         strcat(bookMove, bookHit);
14954         HandleMachineMove(bookMove, &first);
14955     }
14956 }
14957
14958
14959 void
14960 DisplayTwoMachinesTitle ()
14961 {
14962     char buf[MSG_SIZ];
14963     if (appData.matchGames > 0) {
14964         if(appData.tourneyFile[0]) {
14965           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14966                    gameInfo.white, _("vs."), gameInfo.black,
14967                    nextGame+1, appData.matchGames+1,
14968                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14969         } else
14970         if (first.twoMachinesColor[0] == 'w') {
14971           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14972                    gameInfo.white, _("vs."),  gameInfo.black,
14973                    first.matchWins, second.matchWins,
14974                    matchGame - 1 - (first.matchWins + second.matchWins));
14975         } else {
14976           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14977                    gameInfo.white, _("vs."), gameInfo.black,
14978                    second.matchWins, first.matchWins,
14979                    matchGame - 1 - (first.matchWins + second.matchWins));
14980         }
14981     } else {
14982       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14983     }
14984     DisplayTitle(buf);
14985 }
14986
14987 void
14988 SettingsMenuIfReady ()
14989 {
14990   if (second.lastPing != second.lastPong) {
14991     DisplayMessage("", _("Waiting for second chess program"));
14992     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14993     return;
14994   }
14995   ThawUI();
14996   DisplayMessage("", "");
14997   SettingsPopUp(&second);
14998 }
14999
15000 int
15001 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15002 {
15003     char buf[MSG_SIZ];
15004     if (cps->pr == NoProc) {
15005         StartChessProgram(cps);
15006         if (cps->protocolVersion == 1) {
15007           retry();
15008           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15009         } else {
15010           /* kludge: allow timeout for initial "feature" command */
15011           if(retry != TwoMachinesEventIfReady) FreezeUI();
15012           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15013           DisplayMessage("", buf);
15014           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15015         }
15016         return 1;
15017     }
15018     return 0;
15019 }
15020
15021 void
15022 TwoMachinesEvent P((void))
15023 {
15024     int i;
15025     char buf[MSG_SIZ];
15026     ChessProgramState *onmove;
15027     char *bookHit = NULL;
15028     static int stalling = 0;
15029     TimeMark now;
15030     long wait;
15031
15032     if (appData.noChessProgram) return;
15033
15034     switch (gameMode) {
15035       case TwoMachinesPlay:
15036         return;
15037       case MachinePlaysWhite:
15038       case MachinePlaysBlack:
15039         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15040             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15041             return;
15042         }
15043         /* fall through */
15044       case BeginningOfGame:
15045       case PlayFromGameFile:
15046       case EndOfGame:
15047         EditGameEvent();
15048         if (gameMode != EditGame) return;
15049         break;
15050       case EditPosition:
15051         EditPositionDone(TRUE);
15052         break;
15053       case AnalyzeMode:
15054       case AnalyzeFile:
15055         ExitAnalyzeMode();
15056         break;
15057       case EditGame:
15058       default:
15059         break;
15060     }
15061
15062 //    forwardMostMove = currentMove;
15063     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15064     startingEngine = TRUE;
15065
15066     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15067
15068     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15069     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15070       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15071       return;
15072     }
15073   if(!appData.epd) {
15074     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15075
15076     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15077                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15078         startingEngine = matchMode = FALSE;
15079         DisplayError("second engine does not play this", 0);
15080         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15081         EditGameEvent(); // switch back to EditGame mode
15082         return;
15083     }
15084
15085     if(!stalling) {
15086       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15087       SendToProgram("force\n", &second);
15088       stalling = 1;
15089       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15090       return;
15091     }
15092   }
15093     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15094     if(appData.matchPause>10000 || appData.matchPause<10)
15095                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15096     wait = SubtractTimeMarks(&now, &pauseStart);
15097     if(wait < appData.matchPause) {
15098         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15099         return;
15100     }
15101     // we are now committed to starting the game
15102     stalling = 0;
15103     DisplayMessage("", "");
15104   if(!appData.epd) {
15105     if (startedFromSetupPosition) {
15106         SendBoard(&second, backwardMostMove);
15107     if (appData.debugMode) {
15108         fprintf(debugFP, "Two Machines\n");
15109     }
15110     }
15111     for (i = backwardMostMove; i < forwardMostMove; i++) {
15112         SendMoveToProgram(i, &second);
15113     }
15114   }
15115
15116     gameMode = TwoMachinesPlay;
15117     pausing = startingEngine = FALSE;
15118     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15119     SetGameInfo();
15120     DisplayTwoMachinesTitle();
15121     firstMove = TRUE;
15122     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15123         onmove = &first;
15124     } else {
15125         onmove = &second;
15126     }
15127     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15128     SendToProgram(first.computerString, &first);
15129     if (first.sendName) {
15130       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15131       SendToProgram(buf, &first);
15132     }
15133   if(!appData.epd) {
15134     SendToProgram(second.computerString, &second);
15135     if (second.sendName) {
15136       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15137       SendToProgram(buf, &second);
15138     }
15139   }
15140
15141     ResetClocks();
15142     if (!first.sendTime || !second.sendTime) {
15143         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15144         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15145     }
15146     if (onmove->sendTime) {
15147       if (onmove->useColors) {
15148         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15149       }
15150       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15151     }
15152     if (onmove->useColors) {
15153       SendToProgram(onmove->twoMachinesColor, onmove);
15154     }
15155     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15156 //    SendToProgram("go\n", onmove);
15157     onmove->maybeThinking = TRUE;
15158     SetMachineThinkingEnables();
15159
15160     StartClocks();
15161
15162     if(bookHit) { // [HGM] book: simulate book reply
15163         static char bookMove[MSG_SIZ]; // a bit generous?
15164
15165         programStats.nodes = programStats.depth = programStats.time =
15166         programStats.score = programStats.got_only_move = 0;
15167         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15168
15169         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15170         strcat(bookMove, bookHit);
15171         savedMessage = bookMove; // args for deferred call
15172         savedState = onmove;
15173         ScheduleDelayedEvent(DeferredBookMove, 1);
15174     }
15175 }
15176
15177 void
15178 TrainingEvent ()
15179 {
15180     if (gameMode == Training) {
15181       SetTrainingModeOff();
15182       gameMode = PlayFromGameFile;
15183       DisplayMessage("", _("Training mode off"));
15184     } else {
15185       gameMode = Training;
15186       animateTraining = appData.animate;
15187
15188       /* make sure we are not already at the end of the game */
15189       if (currentMove < forwardMostMove) {
15190         SetTrainingModeOn();
15191         DisplayMessage("", _("Training mode on"));
15192       } else {
15193         gameMode = PlayFromGameFile;
15194         DisplayError(_("Already at end of game"), 0);
15195       }
15196     }
15197     ModeHighlight();
15198 }
15199
15200 void
15201 IcsClientEvent ()
15202 {
15203     if (!appData.icsActive) return;
15204     switch (gameMode) {
15205       case IcsPlayingWhite:
15206       case IcsPlayingBlack:
15207       case IcsObserving:
15208       case IcsIdle:
15209       case BeginningOfGame:
15210       case IcsExamining:
15211         return;
15212
15213       case EditGame:
15214         break;
15215
15216       case EditPosition:
15217         EditPositionDone(TRUE);
15218         break;
15219
15220       case AnalyzeMode:
15221       case AnalyzeFile:
15222         ExitAnalyzeMode();
15223         break;
15224
15225       default:
15226         EditGameEvent();
15227         break;
15228     }
15229
15230     gameMode = IcsIdle;
15231     ModeHighlight();
15232     return;
15233 }
15234
15235 void
15236 EditGameEvent ()
15237 {
15238     int i;
15239
15240     switch (gameMode) {
15241       case Training:
15242         SetTrainingModeOff();
15243         break;
15244       case MachinePlaysWhite:
15245       case MachinePlaysBlack:
15246       case BeginningOfGame:
15247         SendToProgram("force\n", &first);
15248         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15249             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15250                 char buf[MSG_SIZ];
15251                 abortEngineThink = TRUE;
15252                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15253                 SendToProgram(buf, &first);
15254                 DisplayMessage("Aborting engine think", "");
15255                 FreezeUI();
15256             }
15257         }
15258         SetUserThinkingEnables();
15259         break;
15260       case PlayFromGameFile:
15261         (void) StopLoadGameTimer();
15262         if (gameFileFP != NULL) {
15263             gameFileFP = NULL;
15264         }
15265         break;
15266       case EditPosition:
15267         EditPositionDone(TRUE);
15268         break;
15269       case AnalyzeMode:
15270       case AnalyzeFile:
15271         ExitAnalyzeMode();
15272         SendToProgram("force\n", &first);
15273         break;
15274       case TwoMachinesPlay:
15275         GameEnds(EndOfFile, NULL, GE_PLAYER);
15276         ResurrectChessProgram();
15277         SetUserThinkingEnables();
15278         break;
15279       case EndOfGame:
15280         ResurrectChessProgram();
15281         break;
15282       case IcsPlayingBlack:
15283       case IcsPlayingWhite:
15284         DisplayError(_("Warning: You are still playing a game"), 0);
15285         break;
15286       case IcsObserving:
15287         DisplayError(_("Warning: You are still observing a game"), 0);
15288         break;
15289       case IcsExamining:
15290         DisplayError(_("Warning: You are still examining a game"), 0);
15291         break;
15292       case IcsIdle:
15293         break;
15294       case EditGame:
15295       default:
15296         return;
15297     }
15298
15299     pausing = FALSE;
15300     StopClocks();
15301     first.offeredDraw = second.offeredDraw = 0;
15302
15303     if (gameMode == PlayFromGameFile) {
15304         whiteTimeRemaining = timeRemaining[0][currentMove];
15305         blackTimeRemaining = timeRemaining[1][currentMove];
15306         DisplayTitle("");
15307     }
15308
15309     if (gameMode == MachinePlaysWhite ||
15310         gameMode == MachinePlaysBlack ||
15311         gameMode == TwoMachinesPlay ||
15312         gameMode == EndOfGame) {
15313         i = forwardMostMove;
15314         while (i > currentMove) {
15315             SendToProgram("undo\n", &first);
15316             i--;
15317         }
15318         if(!adjustedClock) {
15319         whiteTimeRemaining = timeRemaining[0][currentMove];
15320         blackTimeRemaining = timeRemaining[1][currentMove];
15321         DisplayBothClocks();
15322         }
15323         if (whiteFlag || blackFlag) {
15324             whiteFlag = blackFlag = 0;
15325         }
15326         DisplayTitle("");
15327     }
15328
15329     gameMode = EditGame;
15330     ModeHighlight();
15331     SetGameInfo();
15332 }
15333
15334 void
15335 EditPositionEvent ()
15336 {
15337     int i;
15338     if (gameMode == EditPosition) {
15339         EditGameEvent();
15340         return;
15341     }
15342
15343     EditGameEvent();
15344     if (gameMode != EditGame) return;
15345
15346     gameMode = EditPosition;
15347     ModeHighlight();
15348     SetGameInfo();
15349     CopyBoard(rightsBoard, nullBoard);
15350     if (currentMove > 0)
15351       CopyBoard(boards[0], boards[currentMove]);
15352     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15353       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15354
15355     blackPlaysFirst = !WhiteOnMove(currentMove);
15356     ResetClocks();
15357     currentMove = forwardMostMove = backwardMostMove = 0;
15358     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15359     DisplayMove(-1);
15360     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15361 }
15362
15363 void
15364 ExitAnalyzeMode ()
15365 {
15366     /* [DM] icsEngineAnalyze - possible call from other functions */
15367     if (appData.icsEngineAnalyze) {
15368         appData.icsEngineAnalyze = FALSE;
15369
15370         DisplayMessage("",_("Close ICS engine analyze..."));
15371     }
15372     if (first.analysisSupport && first.analyzing) {
15373       SendToBoth("exit\n");
15374       first.analyzing = second.analyzing = FALSE;
15375     }
15376     thinkOutput[0] = NULLCHAR;
15377 }
15378
15379 void
15380 EditPositionDone (Boolean fakeRights)
15381 {
15382     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15383
15384     startedFromSetupPosition = TRUE;
15385     InitChessProgram(&first, FALSE);
15386     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15387       int r, f;
15388       boards[0][EP_STATUS] = EP_NONE;
15389       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15390       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15391         if(rightsBoard[r][f]) {
15392           ChessSquare p = boards[0][r][f];
15393           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15394           else if(p == king) boards[0][CASTLING][2] = f;
15395           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15396           else rightsBoard[r][f] = 2; // mark for second pass
15397         }
15398       }
15399       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15400         if(rightsBoard[r][f] == 2) {
15401           ChessSquare p = boards[0][r][f];
15402           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15403           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15404         }
15405       }
15406     }
15407     SendToProgram("force\n", &first);
15408     if (blackPlaysFirst) {
15409         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15410         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15411         currentMove = forwardMostMove = backwardMostMove = 1;
15412         CopyBoard(boards[1], boards[0]);
15413     } else {
15414         currentMove = forwardMostMove = backwardMostMove = 0;
15415     }
15416     SendBoard(&first, forwardMostMove);
15417     if (appData.debugMode) {
15418         fprintf(debugFP, "EditPosDone\n");
15419     }
15420     DisplayTitle("");
15421     DisplayMessage("", "");
15422     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15423     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15424     gameMode = EditGame;
15425     ModeHighlight();
15426     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15427     ClearHighlights(); /* [AS] */
15428 }
15429
15430 /* Pause for `ms' milliseconds */
15431 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15432 void
15433 TimeDelay (long ms)
15434 {
15435     TimeMark m1, m2;
15436
15437     GetTimeMark(&m1);
15438     do {
15439         GetTimeMark(&m2);
15440     } while (SubtractTimeMarks(&m2, &m1) < ms);
15441 }
15442
15443 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15444 void
15445 SendMultiLineToICS (char *buf)
15446 {
15447     char temp[MSG_SIZ+1], *p;
15448     int len;
15449
15450     len = strlen(buf);
15451     if (len > MSG_SIZ)
15452       len = MSG_SIZ;
15453
15454     strncpy(temp, buf, len);
15455     temp[len] = 0;
15456
15457     p = temp;
15458     while (*p) {
15459         if (*p == '\n' || *p == '\r')
15460           *p = ' ';
15461         ++p;
15462     }
15463
15464     strcat(temp, "\n");
15465     SendToICS(temp);
15466     SendToPlayer(temp, strlen(temp));
15467 }
15468
15469 void
15470 SetWhiteToPlayEvent ()
15471 {
15472     if (gameMode == EditPosition) {
15473         blackPlaysFirst = FALSE;
15474         DisplayBothClocks();    /* works because currentMove is 0 */
15475     } else if (gameMode == IcsExamining) {
15476         SendToICS(ics_prefix);
15477         SendToICS("tomove white\n");
15478     }
15479 }
15480
15481 void
15482 SetBlackToPlayEvent ()
15483 {
15484     if (gameMode == EditPosition) {
15485         blackPlaysFirst = TRUE;
15486         currentMove = 1;        /* kludge */
15487         DisplayBothClocks();
15488         currentMove = 0;
15489     } else if (gameMode == IcsExamining) {
15490         SendToICS(ics_prefix);
15491         SendToICS("tomove black\n");
15492     }
15493 }
15494
15495 void
15496 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15497 {
15498     char buf[MSG_SIZ];
15499     ChessSquare piece = boards[0][y][x];
15500     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15501     static int lastVariant;
15502     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15503
15504     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15505
15506     switch (selection) {
15507       case ClearBoard:
15508         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15509         MarkTargetSquares(1);
15510         CopyBoard(currentBoard, boards[0]);
15511         CopyBoard(menuBoard, initialPosition);
15512         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15513             SendToICS(ics_prefix);
15514             SendToICS("bsetup clear\n");
15515         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15516             SendToICS(ics_prefix);
15517             SendToICS("clearboard\n");
15518         } else {
15519             int nonEmpty = 0;
15520             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15521                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15522                 for (y = 0; y < BOARD_HEIGHT; y++) {
15523                     if (gameMode == IcsExamining) {
15524                         if (boards[currentMove][y][x] != EmptySquare) {
15525                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15526                                     AAA + x, ONE + y);
15527                             SendToICS(buf);
15528                         }
15529                     } else if(boards[0][y][x] != DarkSquare) {
15530                         if(boards[0][y][x] != p) nonEmpty++;
15531                         boards[0][y][x] = p;
15532                     }
15533                 }
15534             }
15535             CopyBoard(rightsBoard, nullBoard);
15536             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15537                 int r, i;
15538                 for(r = 0; r < BOARD_HEIGHT; r++) {
15539                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15540                     ChessSquare p = menuBoard[r][x];
15541                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15542                   }
15543                 }
15544                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15545                 DisplayMessage("Clicking clock again restores position", "");
15546                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15547                 if(!nonEmpty) { // asked to clear an empty board
15548                     CopyBoard(boards[0], menuBoard);
15549                 } else
15550                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15551                     CopyBoard(boards[0], initialPosition);
15552                 } else
15553                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15554                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15555                     CopyBoard(boards[0], erasedBoard);
15556                 } else
15557                     CopyBoard(erasedBoard, currentBoard);
15558
15559                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15560                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15561             }
15562         }
15563         if (gameMode == EditPosition) {
15564             DrawPosition(FALSE, boards[0]);
15565         }
15566         break;
15567
15568       case WhitePlay:
15569         SetWhiteToPlayEvent();
15570         break;
15571
15572       case BlackPlay:
15573         SetBlackToPlayEvent();
15574         break;
15575
15576       case EmptySquare:
15577         if (gameMode == IcsExamining) {
15578             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15579             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15580             SendToICS(buf);
15581         } else {
15582             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15583                 if(x == BOARD_LEFT-2) {
15584                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15585                     boards[0][y][1] = 0;
15586                 } else
15587                 if(x == BOARD_RGHT+1) {
15588                     if(y >= gameInfo.holdingsSize) break;
15589                     boards[0][y][BOARD_WIDTH-2] = 0;
15590                 } else break;
15591             }
15592             boards[0][y][x] = EmptySquare;
15593             DrawPosition(FALSE, boards[0]);
15594         }
15595         break;
15596
15597       case PromotePiece:
15598         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15599            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15600             selection = (ChessSquare) (PROMOTED(piece));
15601         } else if(piece == EmptySquare) selection = WhiteSilver;
15602         else selection = (ChessSquare)((int)piece - 1);
15603         goto defaultlabel;
15604
15605       case DemotePiece:
15606         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15607            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15608             selection = (ChessSquare) (DEMOTED(piece));
15609         } else if(piece == EmptySquare) selection = BlackSilver;
15610         else selection = (ChessSquare)((int)piece + 1);
15611         goto defaultlabel;
15612
15613       case WhiteQueen:
15614       case BlackQueen:
15615         if(gameInfo.variant == VariantShatranj ||
15616            gameInfo.variant == VariantXiangqi  ||
15617            gameInfo.variant == VariantCourier  ||
15618            gameInfo.variant == VariantASEAN    ||
15619            gameInfo.variant == VariantMakruk     )
15620             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15621         goto defaultlabel;
15622
15623       case WhiteRook:
15624         baseRank = 0;
15625       case BlackRook:
15626         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15627         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15628         goto defaultlabel;
15629
15630       case WhiteKing:
15631         baseRank = 0;
15632       case BlackKing:
15633         if(gameInfo.variant == VariantXiangqi)
15634             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15635         if(gameInfo.variant == VariantKnightmate)
15636             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15637         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15638       default:
15639         defaultlabel:
15640         if (gameMode == IcsExamining) {
15641             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15642             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15643                      PieceToChar(selection), AAA + x, ONE + y);
15644             SendToICS(buf);
15645         } else {
15646             rightsBoard[y][x] = hasRights;
15647             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15648                 int n;
15649                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15650                     n = PieceToNumber(selection - BlackPawn);
15651                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15652                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15653                     boards[0][BOARD_HEIGHT-1-n][1]++;
15654                 } else
15655                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15656                     n = PieceToNumber(selection);
15657                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15658                     boards[0][n][BOARD_WIDTH-1] = selection;
15659                     boards[0][n][BOARD_WIDTH-2]++;
15660                 }
15661             } else
15662             boards[0][y][x] = selection;
15663             DrawPosition(TRUE, boards[0]);
15664             ClearHighlights();
15665             fromX = fromY = -1;
15666         }
15667         break;
15668     }
15669 }
15670
15671
15672 void
15673 DropMenuEvent (ChessSquare selection, int x, int y)
15674 {
15675     ChessMove moveType;
15676
15677     switch (gameMode) {
15678       case IcsPlayingWhite:
15679       case MachinePlaysBlack:
15680         if (!WhiteOnMove(currentMove)) {
15681             DisplayMoveError(_("It is Black's turn"));
15682             return;
15683         }
15684         moveType = WhiteDrop;
15685         break;
15686       case IcsPlayingBlack:
15687       case MachinePlaysWhite:
15688         if (WhiteOnMove(currentMove)) {
15689             DisplayMoveError(_("It is White's turn"));
15690             return;
15691         }
15692         moveType = BlackDrop;
15693         break;
15694       case EditGame:
15695         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15696         break;
15697       default:
15698         return;
15699     }
15700
15701     if (moveType == BlackDrop && selection < BlackPawn) {
15702       selection = (ChessSquare) ((int) selection
15703                                  + (int) BlackPawn - (int) WhitePawn);
15704     }
15705     if (boards[currentMove][y][x] != EmptySquare) {
15706         DisplayMoveError(_("That square is occupied"));
15707         return;
15708     }
15709
15710     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15711 }
15712
15713 void
15714 AcceptEvent ()
15715 {
15716     /* Accept a pending offer of any kind from opponent */
15717
15718     if (appData.icsActive) {
15719         SendToICS(ics_prefix);
15720         SendToICS("accept\n");
15721     } else if (cmailMsgLoaded) {
15722         if (currentMove == cmailOldMove &&
15723             commentList[cmailOldMove] != NULL &&
15724             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15725                    "Black offers a draw" : "White offers a draw")) {
15726             TruncateGame();
15727             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15728             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15729         } else {
15730             DisplayError(_("There is no pending offer on this move"), 0);
15731             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15732         }
15733     } else {
15734         /* Not used for offers from chess program */
15735     }
15736 }
15737
15738 void
15739 DeclineEvent ()
15740 {
15741     /* Decline a pending offer of any kind from opponent */
15742
15743     if (appData.icsActive) {
15744         SendToICS(ics_prefix);
15745         SendToICS("decline\n");
15746     } else if (cmailMsgLoaded) {
15747         if (currentMove == cmailOldMove &&
15748             commentList[cmailOldMove] != NULL &&
15749             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15750                    "Black offers a draw" : "White offers a draw")) {
15751 #ifdef NOTDEF
15752             AppendComment(cmailOldMove, "Draw declined", TRUE);
15753             DisplayComment(cmailOldMove - 1, "Draw declined");
15754 #endif /*NOTDEF*/
15755         } else {
15756             DisplayError(_("There is no pending offer on this move"), 0);
15757         }
15758     } else {
15759         /* Not used for offers from chess program */
15760     }
15761 }
15762
15763 void
15764 RematchEvent ()
15765 {
15766     /* Issue ICS rematch command */
15767     if (appData.icsActive) {
15768         SendToICS(ics_prefix);
15769         SendToICS("rematch\n");
15770     }
15771 }
15772
15773 void
15774 CallFlagEvent ()
15775 {
15776     /* Call your opponent's flag (claim a win on time) */
15777     if (appData.icsActive) {
15778         SendToICS(ics_prefix);
15779         SendToICS("flag\n");
15780     } else {
15781         switch (gameMode) {
15782           default:
15783             return;
15784           case MachinePlaysWhite:
15785             if (whiteFlag) {
15786                 if (blackFlag)
15787                   GameEnds(GameIsDrawn, "Both players ran out of time",
15788                            GE_PLAYER);
15789                 else
15790                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15791             } else {
15792                 DisplayError(_("Your opponent is not out of time"), 0);
15793             }
15794             break;
15795           case MachinePlaysBlack:
15796             if (blackFlag) {
15797                 if (whiteFlag)
15798                   GameEnds(GameIsDrawn, "Both players ran out of time",
15799                            GE_PLAYER);
15800                 else
15801                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15802             } else {
15803                 DisplayError(_("Your opponent is not out of time"), 0);
15804             }
15805             break;
15806         }
15807     }
15808 }
15809
15810 void
15811 ClockClick (int which)
15812 {       // [HGM] code moved to back-end from winboard.c
15813         if(which) { // black clock
15814           if (gameMode == EditPosition || gameMode == IcsExamining) {
15815             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15816             SetBlackToPlayEvent();
15817           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15818                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15819           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15820           } else if (shiftKey) {
15821             AdjustClock(which, -1);
15822           } else if (gameMode == IcsPlayingWhite ||
15823                      gameMode == MachinePlaysBlack) {
15824             CallFlagEvent();
15825           }
15826         } else { // white clock
15827           if (gameMode == EditPosition || gameMode == IcsExamining) {
15828             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15829             SetWhiteToPlayEvent();
15830           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15831                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15832           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15833           } else if (shiftKey) {
15834             AdjustClock(which, -1);
15835           } else if (gameMode == IcsPlayingBlack ||
15836                    gameMode == MachinePlaysWhite) {
15837             CallFlagEvent();
15838           }
15839         }
15840 }
15841
15842 void
15843 DrawEvent ()
15844 {
15845     /* Offer draw or accept pending draw offer from opponent */
15846
15847     if (appData.icsActive) {
15848         /* Note: tournament rules require draw offers to be
15849            made after you make your move but before you punch
15850            your clock.  Currently ICS doesn't let you do that;
15851            instead, you immediately punch your clock after making
15852            a move, but you can offer a draw at any time. */
15853
15854         SendToICS(ics_prefix);
15855         SendToICS("draw\n");
15856         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15857     } else if (cmailMsgLoaded) {
15858         if (currentMove == cmailOldMove &&
15859             commentList[cmailOldMove] != NULL &&
15860             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15861                    "Black offers a draw" : "White offers a draw")) {
15862             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15863             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15864         } else if (currentMove == cmailOldMove + 1) {
15865             char *offer = WhiteOnMove(cmailOldMove) ?
15866               "White offers a draw" : "Black offers a draw";
15867             AppendComment(currentMove, offer, TRUE);
15868             DisplayComment(currentMove - 1, offer);
15869             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15870         } else {
15871             DisplayError(_("You must make your move before offering a draw"), 0);
15872             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15873         }
15874     } else if (first.offeredDraw) {
15875         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15876     } else {
15877         if (first.sendDrawOffers) {
15878             SendToProgram("draw\n", &first);
15879             userOfferedDraw = TRUE;
15880         }
15881     }
15882 }
15883
15884 void
15885 AdjournEvent ()
15886 {
15887     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15888
15889     if (appData.icsActive) {
15890         SendToICS(ics_prefix);
15891         SendToICS("adjourn\n");
15892     } else {
15893         /* Currently GNU Chess doesn't offer or accept Adjourns */
15894     }
15895 }
15896
15897
15898 void
15899 AbortEvent ()
15900 {
15901     /* Offer Abort or accept pending Abort offer from opponent */
15902
15903     if (appData.icsActive) {
15904         SendToICS(ics_prefix);
15905         SendToICS("abort\n");
15906     } else {
15907         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15908     }
15909 }
15910
15911 void
15912 ResignEvent ()
15913 {
15914     /* Resign.  You can do this even if it's not your turn. */
15915
15916     if (appData.icsActive) {
15917         SendToICS(ics_prefix);
15918         SendToICS("resign\n");
15919     } else {
15920         switch (gameMode) {
15921           case MachinePlaysWhite:
15922             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15923             break;
15924           case MachinePlaysBlack:
15925             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15926             break;
15927           case EditGame:
15928             if (cmailMsgLoaded) {
15929                 TruncateGame();
15930                 if (WhiteOnMove(cmailOldMove)) {
15931                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15932                 } else {
15933                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15934                 }
15935                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15936             }
15937             break;
15938           default:
15939             break;
15940         }
15941     }
15942 }
15943
15944
15945 void
15946 StopObservingEvent ()
15947 {
15948     /* Stop observing current games */
15949     SendToICS(ics_prefix);
15950     SendToICS("unobserve\n");
15951 }
15952
15953 void
15954 StopExaminingEvent ()
15955 {
15956     /* Stop observing current game */
15957     SendToICS(ics_prefix);
15958     SendToICS("unexamine\n");
15959 }
15960
15961 void
15962 ForwardInner (int target)
15963 {
15964     int limit; int oldSeekGraphUp = seekGraphUp;
15965
15966     if (appData.debugMode)
15967         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15968                 target, currentMove, forwardMostMove);
15969
15970     if (gameMode == EditPosition)
15971       return;
15972
15973     seekGraphUp = FALSE;
15974     MarkTargetSquares(1);
15975     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15976
15977     if (gameMode == PlayFromGameFile && !pausing)
15978       PauseEvent();
15979
15980     if (gameMode == IcsExamining && pausing)
15981       limit = pauseExamForwardMostMove;
15982     else
15983       limit = forwardMostMove;
15984
15985     if (target > limit) target = limit;
15986
15987     if (target > 0 && moveList[target - 1][0]) {
15988         int fromX, fromY, toX, toY;
15989         toX = moveList[target - 1][2] - AAA;
15990         toY = moveList[target - 1][3] - ONE;
15991         if (moveList[target - 1][1] == '@') {
15992             if (appData.highlightLastMove) {
15993                 SetHighlights(-1, -1, toX, toY);
15994             }
15995         } else {
15996             fromX = moveList[target - 1][0] - AAA;
15997             fromY = moveList[target - 1][1] - ONE;
15998             if (target == currentMove + 1) {
15999                 if(moveList[target - 1][4] == ';') { // multi-leg
16000                     killX = moveList[target - 1][5] - AAA;
16001                     killY = moveList[target - 1][6] - ONE;
16002                 }
16003                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16004                 killX = killY = -1;
16005             }
16006             if (appData.highlightLastMove) {
16007                 SetHighlights(fromX, fromY, toX, toY);
16008             }
16009         }
16010     }
16011     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16012         gameMode == Training || gameMode == PlayFromGameFile ||
16013         gameMode == AnalyzeFile) {
16014         while (currentMove < target) {
16015             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16016             SendMoveToProgram(currentMove++, &first);
16017         }
16018     } else {
16019         currentMove = target;
16020     }
16021
16022     if (gameMode == EditGame || gameMode == EndOfGame) {
16023         whiteTimeRemaining = timeRemaining[0][currentMove];
16024         blackTimeRemaining = timeRemaining[1][currentMove];
16025     }
16026     DisplayBothClocks();
16027     DisplayMove(currentMove - 1);
16028     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16029     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16030     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16031         DisplayComment(currentMove - 1, commentList[currentMove]);
16032     }
16033     ClearMap(); // [HGM] exclude: invalidate map
16034 }
16035
16036
16037 void
16038 ForwardEvent ()
16039 {
16040     if (gameMode == IcsExamining && !pausing) {
16041         SendToICS(ics_prefix);
16042         SendToICS("forward\n");
16043     } else {
16044         ForwardInner(currentMove + 1);
16045     }
16046 }
16047
16048 void
16049 ToEndEvent ()
16050 {
16051     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16052         /* to optimze, we temporarily turn off analysis mode while we feed
16053          * the remaining moves to the engine. Otherwise we get analysis output
16054          * after each move.
16055          */
16056         if (first.analysisSupport) {
16057           SendToProgram("exit\nforce\n", &first);
16058           first.analyzing = FALSE;
16059         }
16060     }
16061
16062     if (gameMode == IcsExamining && !pausing) {
16063         SendToICS(ics_prefix);
16064         SendToICS("forward 999999\n");
16065     } else {
16066         ForwardInner(forwardMostMove);
16067     }
16068
16069     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16070         /* we have fed all the moves, so reactivate analysis mode */
16071         SendToProgram("analyze\n", &first);
16072         first.analyzing = TRUE;
16073         /*first.maybeThinking = TRUE;*/
16074         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16075     }
16076 }
16077
16078 void
16079 BackwardInner (int target)
16080 {
16081     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16082
16083     if (appData.debugMode)
16084         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16085                 target, currentMove, forwardMostMove);
16086
16087     if (gameMode == EditPosition) return;
16088     seekGraphUp = FALSE;
16089     MarkTargetSquares(1);
16090     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16091     if (currentMove <= backwardMostMove) {
16092         ClearHighlights();
16093         DrawPosition(full_redraw, boards[currentMove]);
16094         return;
16095     }
16096     if (gameMode == PlayFromGameFile && !pausing)
16097       PauseEvent();
16098
16099     if (moveList[target][0]) {
16100         int fromX, fromY, toX, toY;
16101         toX = moveList[target][2] - AAA;
16102         toY = moveList[target][3] - ONE;
16103         if (moveList[target][1] == '@') {
16104             if (appData.highlightLastMove) {
16105                 SetHighlights(-1, -1, toX, toY);
16106             }
16107         } else {
16108             fromX = moveList[target][0] - AAA;
16109             fromY = moveList[target][1] - ONE;
16110             if (target == currentMove - 1) {
16111                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16112             }
16113             if (appData.highlightLastMove) {
16114                 SetHighlights(fromX, fromY, toX, toY);
16115             }
16116         }
16117     }
16118     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16119         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16120         while (currentMove > target) {
16121             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16122                 // null move cannot be undone. Reload program with move history before it.
16123                 int i;
16124                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16125                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16126                 }
16127                 SendBoard(&first, i);
16128               if(second.analyzing) SendBoard(&second, i);
16129                 for(currentMove=i; currentMove<target; currentMove++) {
16130                     SendMoveToProgram(currentMove, &first);
16131                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16132                 }
16133                 break;
16134             }
16135             SendToBoth("undo\n");
16136             currentMove--;
16137         }
16138     } else {
16139         currentMove = target;
16140     }
16141
16142     if (gameMode == EditGame || gameMode == EndOfGame) {
16143         whiteTimeRemaining = timeRemaining[0][currentMove];
16144         blackTimeRemaining = timeRemaining[1][currentMove];
16145     }
16146     DisplayBothClocks();
16147     DisplayMove(currentMove - 1);
16148     DrawPosition(full_redraw, boards[currentMove]);
16149     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16150     // [HGM] PV info: routine tests if comment empty
16151     DisplayComment(currentMove - 1, commentList[currentMove]);
16152     ClearMap(); // [HGM] exclude: invalidate map
16153 }
16154
16155 void
16156 BackwardEvent ()
16157 {
16158     if (gameMode == IcsExamining && !pausing) {
16159         SendToICS(ics_prefix);
16160         SendToICS("backward\n");
16161     } else {
16162         BackwardInner(currentMove - 1);
16163     }
16164 }
16165
16166 void
16167 ToStartEvent ()
16168 {
16169     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16170         /* to optimize, we temporarily turn off analysis mode while we undo
16171          * all the moves. Otherwise we get analysis output after each undo.
16172          */
16173         if (first.analysisSupport) {
16174           SendToProgram("exit\nforce\n", &first);
16175           first.analyzing = FALSE;
16176         }
16177     }
16178
16179     if (gameMode == IcsExamining && !pausing) {
16180         SendToICS(ics_prefix);
16181         SendToICS("backward 999999\n");
16182     } else {
16183         BackwardInner(backwardMostMove);
16184     }
16185
16186     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16187         /* we have fed all the moves, so reactivate analysis mode */
16188         SendToProgram("analyze\n", &first);
16189         first.analyzing = TRUE;
16190         /*first.maybeThinking = TRUE;*/
16191         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16192     }
16193 }
16194
16195 void
16196 ToNrEvent (int to)
16197 {
16198   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16199   if (to >= forwardMostMove) to = forwardMostMove;
16200   if (to <= backwardMostMove) to = backwardMostMove;
16201   if (to < currentMove) {
16202     BackwardInner(to);
16203   } else {
16204     ForwardInner(to);
16205   }
16206 }
16207
16208 void
16209 RevertEvent (Boolean annotate)
16210 {
16211     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16212         return;
16213     }
16214     if (gameMode != IcsExamining) {
16215         DisplayError(_("You are not examining a game"), 0);
16216         return;
16217     }
16218     if (pausing) {
16219         DisplayError(_("You can't revert while pausing"), 0);
16220         return;
16221     }
16222     SendToICS(ics_prefix);
16223     SendToICS("revert\n");
16224 }
16225
16226 void
16227 RetractMoveEvent ()
16228 {
16229     switch (gameMode) {
16230       case MachinePlaysWhite:
16231       case MachinePlaysBlack:
16232         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16233             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16234             return;
16235         }
16236         if (forwardMostMove < 2) return;
16237         currentMove = forwardMostMove = forwardMostMove - 2;
16238         whiteTimeRemaining = timeRemaining[0][currentMove];
16239         blackTimeRemaining = timeRemaining[1][currentMove];
16240         DisplayBothClocks();
16241         DisplayMove(currentMove - 1);
16242         ClearHighlights();/*!! could figure this out*/
16243         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16244         SendToProgram("remove\n", &first);
16245         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16246         break;
16247
16248       case BeginningOfGame:
16249       default:
16250         break;
16251
16252       case IcsPlayingWhite:
16253       case IcsPlayingBlack:
16254         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16255             SendToICS(ics_prefix);
16256             SendToICS("takeback 2\n");
16257         } else {
16258             SendToICS(ics_prefix);
16259             SendToICS("takeback 1\n");
16260         }
16261         break;
16262     }
16263 }
16264
16265 void
16266 MoveNowEvent ()
16267 {
16268     ChessProgramState *cps;
16269
16270     switch (gameMode) {
16271       case MachinePlaysWhite:
16272         if (!WhiteOnMove(forwardMostMove)) {
16273             DisplayError(_("It is your turn"), 0);
16274             return;
16275         }
16276         cps = &first;
16277         break;
16278       case MachinePlaysBlack:
16279         if (WhiteOnMove(forwardMostMove)) {
16280             DisplayError(_("It is your turn"), 0);
16281             return;
16282         }
16283         cps = &first;
16284         break;
16285       case TwoMachinesPlay:
16286         if (WhiteOnMove(forwardMostMove) ==
16287             (first.twoMachinesColor[0] == 'w')) {
16288             cps = &first;
16289         } else {
16290             cps = &second;
16291         }
16292         break;
16293       case BeginningOfGame:
16294       default:
16295         return;
16296     }
16297     SendToProgram("?\n", cps);
16298 }
16299
16300 void
16301 TruncateGameEvent ()
16302 {
16303     EditGameEvent();
16304     if (gameMode != EditGame) return;
16305     TruncateGame();
16306 }
16307
16308 void
16309 TruncateGame ()
16310 {
16311     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16312     if (forwardMostMove > currentMove) {
16313         if (gameInfo.resultDetails != NULL) {
16314             free(gameInfo.resultDetails);
16315             gameInfo.resultDetails = NULL;
16316             gameInfo.result = GameUnfinished;
16317         }
16318         forwardMostMove = currentMove;
16319         HistorySet(parseList, backwardMostMove, forwardMostMove,
16320                    currentMove-1);
16321     }
16322 }
16323
16324 void
16325 HintEvent ()
16326 {
16327     if (appData.noChessProgram) return;
16328     switch (gameMode) {
16329       case MachinePlaysWhite:
16330         if (WhiteOnMove(forwardMostMove)) {
16331             DisplayError(_("Wait until your turn."), 0);
16332             return;
16333         }
16334         break;
16335       case BeginningOfGame:
16336       case MachinePlaysBlack:
16337         if (!WhiteOnMove(forwardMostMove)) {
16338             DisplayError(_("Wait until your turn."), 0);
16339             return;
16340         }
16341         break;
16342       default:
16343         DisplayError(_("No hint available"), 0);
16344         return;
16345     }
16346     SendToProgram("hint\n", &first);
16347     hintRequested = TRUE;
16348 }
16349
16350 int
16351 SaveSelected (FILE *g, int dummy, char *dummy2)
16352 {
16353     ListGame * lg = (ListGame *) gameList.head;
16354     int nItem, cnt=0;
16355     FILE *f;
16356
16357     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16358         DisplayError(_("Game list not loaded or empty"), 0);
16359         return 0;
16360     }
16361
16362     creatingBook = TRUE; // suppresses stuff during load game
16363
16364     /* Get list size */
16365     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16366         if(lg->position >= 0) { // selected?
16367             LoadGame(f, nItem, "", TRUE);
16368             SaveGamePGN2(g); // leaves g open
16369             cnt++; DoEvents();
16370         }
16371         lg = (ListGame *) lg->node.succ;
16372     }
16373
16374     fclose(g);
16375     creatingBook = FALSE;
16376
16377     return cnt;
16378 }
16379
16380 void
16381 CreateBookEvent ()
16382 {
16383     ListGame * lg = (ListGame *) gameList.head;
16384     FILE *f, *g;
16385     int nItem;
16386     static int secondTime = FALSE;
16387
16388     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16389         DisplayError(_("Game list not loaded or empty"), 0);
16390         return;
16391     }
16392
16393     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16394         fclose(g);
16395         secondTime++;
16396         DisplayNote(_("Book file exists! Try again for overwrite."));
16397         return;
16398     }
16399
16400     creatingBook = TRUE;
16401     secondTime = FALSE;
16402
16403     /* Get list size */
16404     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16405         if(lg->position >= 0) {
16406             LoadGame(f, nItem, "", TRUE);
16407             AddGameToBook(TRUE);
16408             DoEvents();
16409         }
16410         lg = (ListGame *) lg->node.succ;
16411     }
16412
16413     creatingBook = FALSE;
16414     FlushBook();
16415 }
16416
16417 void
16418 BookEvent ()
16419 {
16420     if (appData.noChessProgram) return;
16421     switch (gameMode) {
16422       case MachinePlaysWhite:
16423         if (WhiteOnMove(forwardMostMove)) {
16424             DisplayError(_("Wait until your turn."), 0);
16425             return;
16426         }
16427         break;
16428       case BeginningOfGame:
16429       case MachinePlaysBlack:
16430         if (!WhiteOnMove(forwardMostMove)) {
16431             DisplayError(_("Wait until your turn."), 0);
16432             return;
16433         }
16434         break;
16435       case EditPosition:
16436         EditPositionDone(TRUE);
16437         break;
16438       case TwoMachinesPlay:
16439         return;
16440       default:
16441         break;
16442     }
16443     SendToProgram("bk\n", &first);
16444     bookOutput[0] = NULLCHAR;
16445     bookRequested = TRUE;
16446 }
16447
16448 void
16449 AboutGameEvent ()
16450 {
16451     char *tags = PGNTags(&gameInfo);
16452     TagsPopUp(tags, CmailMsg());
16453     free(tags);
16454 }
16455
16456 /* end button procedures */
16457
16458 void
16459 PrintPosition (FILE *fp, int move)
16460 {
16461     int i, j;
16462
16463     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16464         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16465             char c = PieceToChar(boards[move][i][j]);
16466             fputc(c == '?' ? '.' : c, fp);
16467             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16468         }
16469     }
16470     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16471       fprintf(fp, "white to play\n");
16472     else
16473       fprintf(fp, "black to play\n");
16474 }
16475
16476 void
16477 PrintOpponents (FILE *fp)
16478 {
16479     if (gameInfo.white != NULL) {
16480         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16481     } else {
16482         fprintf(fp, "\n");
16483     }
16484 }
16485
16486 /* Find last component of program's own name, using some heuristics */
16487 void
16488 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16489 {
16490     char *p, *q, c;
16491     int local = (strcmp(host, "localhost") == 0);
16492     while (!local && (p = strchr(prog, ';')) != NULL) {
16493         p++;
16494         while (*p == ' ') p++;
16495         prog = p;
16496     }
16497     if (*prog == '"' || *prog == '\'') {
16498         q = strchr(prog + 1, *prog);
16499     } else {
16500         q = strchr(prog, ' ');
16501     }
16502     if (q == NULL) q = prog + strlen(prog);
16503     p = q;
16504     while (p >= prog && *p != '/' && *p != '\\') p--;
16505     p++;
16506     if(p == prog && *p == '"') p++;
16507     c = *q; *q = 0;
16508     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16509     memcpy(buf, p, q - p);
16510     buf[q - p] = NULLCHAR;
16511     if (!local) {
16512         strcat(buf, "@");
16513         strcat(buf, host);
16514     }
16515 }
16516
16517 char *
16518 TimeControlTagValue ()
16519 {
16520     char buf[MSG_SIZ];
16521     if (!appData.clockMode) {
16522       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16523     } else if (movesPerSession > 0) {
16524       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16525     } else if (timeIncrement == 0) {
16526       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16527     } else {
16528       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16529     }
16530     return StrSave(buf);
16531 }
16532
16533 void
16534 SetGameInfo ()
16535 {
16536     /* This routine is used only for certain modes */
16537     VariantClass v = gameInfo.variant;
16538     ChessMove r = GameUnfinished;
16539     char *p = NULL;
16540
16541     if(keepInfo) return;
16542
16543     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16544         r = gameInfo.result;
16545         p = gameInfo.resultDetails;
16546         gameInfo.resultDetails = NULL;
16547     }
16548     ClearGameInfo(&gameInfo);
16549     gameInfo.variant = v;
16550
16551     switch (gameMode) {
16552       case MachinePlaysWhite:
16553         gameInfo.event = StrSave( appData.pgnEventHeader );
16554         gameInfo.site = StrSave(HostName());
16555         gameInfo.date = PGNDate();
16556         gameInfo.round = StrSave("-");
16557         gameInfo.white = StrSave(first.tidy);
16558         gameInfo.black = StrSave(UserName());
16559         gameInfo.timeControl = TimeControlTagValue();
16560         break;
16561
16562       case MachinePlaysBlack:
16563         gameInfo.event = StrSave( appData.pgnEventHeader );
16564         gameInfo.site = StrSave(HostName());
16565         gameInfo.date = PGNDate();
16566         gameInfo.round = StrSave("-");
16567         gameInfo.white = StrSave(UserName());
16568         gameInfo.black = StrSave(first.tidy);
16569         gameInfo.timeControl = TimeControlTagValue();
16570         break;
16571
16572       case TwoMachinesPlay:
16573         gameInfo.event = StrSave( appData.pgnEventHeader );
16574         gameInfo.site = StrSave(HostName());
16575         gameInfo.date = PGNDate();
16576         if (roundNr > 0) {
16577             char buf[MSG_SIZ];
16578             snprintf(buf, MSG_SIZ, "%d", roundNr);
16579             gameInfo.round = StrSave(buf);
16580         } else {
16581             gameInfo.round = StrSave("-");
16582         }
16583         if (first.twoMachinesColor[0] == 'w') {
16584             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16585             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16586         } else {
16587             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16588             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16589         }
16590         gameInfo.timeControl = TimeControlTagValue();
16591         break;
16592
16593       case EditGame:
16594         gameInfo.event = StrSave("Edited game");
16595         gameInfo.site = StrSave(HostName());
16596         gameInfo.date = PGNDate();
16597         gameInfo.round = StrSave("-");
16598         gameInfo.white = StrSave("-");
16599         gameInfo.black = StrSave("-");
16600         gameInfo.result = r;
16601         gameInfo.resultDetails = p;
16602         break;
16603
16604       case EditPosition:
16605         gameInfo.event = StrSave("Edited position");
16606         gameInfo.site = StrSave(HostName());
16607         gameInfo.date = PGNDate();
16608         gameInfo.round = StrSave("-");
16609         gameInfo.white = StrSave("-");
16610         gameInfo.black = StrSave("-");
16611         break;
16612
16613       case IcsPlayingWhite:
16614       case IcsPlayingBlack:
16615       case IcsObserving:
16616       case IcsExamining:
16617         break;
16618
16619       case PlayFromGameFile:
16620         gameInfo.event = StrSave("Game from non-PGN file");
16621         gameInfo.site = StrSave(HostName());
16622         gameInfo.date = PGNDate();
16623         gameInfo.round = StrSave("-");
16624         gameInfo.white = StrSave("?");
16625         gameInfo.black = StrSave("?");
16626         break;
16627
16628       default:
16629         break;
16630     }
16631 }
16632
16633 void
16634 ReplaceComment (int index, char *text)
16635 {
16636     int len;
16637     char *p;
16638     float score;
16639
16640     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16641        pvInfoList[index-1].depth == len &&
16642        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16643        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16644     while (*text == '\n') text++;
16645     len = strlen(text);
16646     while (len > 0 && text[len - 1] == '\n') len--;
16647
16648     if (commentList[index] != NULL)
16649       free(commentList[index]);
16650
16651     if (len == 0) {
16652         commentList[index] = NULL;
16653         return;
16654     }
16655   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16656       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16657       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16658     commentList[index] = (char *) malloc(len + 2);
16659     strncpy(commentList[index], text, len);
16660     commentList[index][len] = '\n';
16661     commentList[index][len + 1] = NULLCHAR;
16662   } else {
16663     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16664     char *p;
16665     commentList[index] = (char *) malloc(len + 7);
16666     safeStrCpy(commentList[index], "{\n", 3);
16667     safeStrCpy(commentList[index]+2, text, len+1);
16668     commentList[index][len+2] = NULLCHAR;
16669     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16670     strcat(commentList[index], "\n}\n");
16671   }
16672 }
16673
16674 void
16675 CrushCRs (char *text)
16676 {
16677   char *p = text;
16678   char *q = text;
16679   char ch;
16680
16681   do {
16682     ch = *p++;
16683     if (ch == '\r') continue;
16684     *q++ = ch;
16685   } while (ch != '\0');
16686 }
16687
16688 void
16689 AppendComment (int index, char *text, Boolean addBraces)
16690 /* addBraces  tells if we should add {} */
16691 {
16692     int oldlen, len;
16693     char *old;
16694
16695 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16696     if(addBraces == 3) addBraces = 0; else // force appending literally
16697     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16698
16699     CrushCRs(text);
16700     while (*text == '\n') text++;
16701     len = strlen(text);
16702     while (len > 0 && text[len - 1] == '\n') len--;
16703     text[len] = NULLCHAR;
16704
16705     if (len == 0) return;
16706
16707     if (commentList[index] != NULL) {
16708       Boolean addClosingBrace = addBraces;
16709         old = commentList[index];
16710         oldlen = strlen(old);
16711         while(commentList[index][oldlen-1] ==  '\n')
16712           commentList[index][--oldlen] = NULLCHAR;
16713         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16714         safeStrCpy(commentList[index], old, oldlen + len + 6);
16715         free(old);
16716         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16717         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16718           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16719           while (*text == '\n') { text++; len--; }
16720           commentList[index][--oldlen] = NULLCHAR;
16721       }
16722         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16723         else          strcat(commentList[index], "\n");
16724         strcat(commentList[index], text);
16725         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16726         else          strcat(commentList[index], "\n");
16727     } else {
16728         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16729         if(addBraces)
16730           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16731         else commentList[index][0] = NULLCHAR;
16732         strcat(commentList[index], text);
16733         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16734         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16735     }
16736 }
16737
16738 static char *
16739 FindStr (char * text, char * sub_text)
16740 {
16741     char * result = strstr( text, sub_text );
16742
16743     if( result != NULL ) {
16744         result += strlen( sub_text );
16745     }
16746
16747     return result;
16748 }
16749
16750 /* [AS] Try to extract PV info from PGN comment */
16751 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16752 char *
16753 GetInfoFromComment (int index, char * text)
16754 {
16755     char * sep = text, *p;
16756
16757     if( text != NULL && index > 0 ) {
16758         int score = 0;
16759         int depth = 0;
16760         int time = -1, sec = 0, deci;
16761         char * s_eval = FindStr( text, "[%eval " );
16762         char * s_emt = FindStr( text, "[%emt " );
16763 #if 0
16764         if( s_eval != NULL || s_emt != NULL ) {
16765 #else
16766         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16767 #endif
16768             /* New style */
16769             char delim;
16770
16771             if( s_eval != NULL ) {
16772                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16773                     return text;
16774                 }
16775
16776                 if( delim != ']' ) {
16777                     return text;
16778                 }
16779             }
16780
16781             if( s_emt != NULL ) {
16782             }
16783                 return text;
16784         }
16785         else {
16786             /* We expect something like: [+|-]nnn.nn/dd */
16787             int score_lo = 0;
16788
16789             if(*text != '{') return text; // [HGM] braces: must be normal comment
16790
16791             sep = strchr( text, '/' );
16792             if( sep == NULL || sep < (text+4) ) {
16793                 return text;
16794             }
16795
16796             p = text;
16797             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16798             if(p[1] == '(') { // comment starts with PV
16799                p = strchr(p, ')'); // locate end of PV
16800                if(p == NULL || sep < p+5) return text;
16801                // at this point we have something like "{(.*) +0.23/6 ..."
16802                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16803                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16804                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16805             }
16806             time = -1; sec = -1; deci = -1;
16807             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16808                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16809                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16810                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16811                 return text;
16812             }
16813
16814             if( score_lo < 0 || score_lo >= 100 ) {
16815                 return text;
16816             }
16817
16818             if(sec >= 0) time = 600*time + 10*sec; else
16819             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16820
16821             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16822
16823             /* [HGM] PV time: now locate end of PV info */
16824             while( *++sep >= '0' && *sep <= '9'); // strip depth
16825             if(time >= 0)
16826             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16827             if(sec >= 0)
16828             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16829             if(deci >= 0)
16830             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16831             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16832         }
16833
16834         if( depth <= 0 ) {
16835             return text;
16836         }
16837
16838         if( time < 0 ) {
16839             time = -1;
16840         }
16841
16842         pvInfoList[index-1].depth = depth;
16843         pvInfoList[index-1].score = score;
16844         pvInfoList[index-1].time  = 10*time; // centi-sec
16845         if(*sep == '}') *sep = 0; else *--sep = '{';
16846         if(p != text) {
16847             while(*p++ = *sep++)
16848                                 ;
16849             sep = text;
16850         } // squeeze out space between PV and comment, and return both
16851     }
16852     return sep;
16853 }
16854
16855 void
16856 SendToProgram (char *message, ChessProgramState *cps)
16857 {
16858     int count, outCount, error;
16859     char buf[MSG_SIZ];
16860
16861     if (cps->pr == NoProc) return;
16862     Attention(cps);
16863
16864     if (appData.debugMode) {
16865         TimeMark now;
16866         GetTimeMark(&now);
16867         fprintf(debugFP, "%ld >%-6s: %s",
16868                 SubtractTimeMarks(&now, &programStartTime),
16869                 cps->which, message);
16870         if(serverFP)
16871             fprintf(serverFP, "%ld >%-6s: %s",
16872                 SubtractTimeMarks(&now, &programStartTime),
16873                 cps->which, message), fflush(serverFP);
16874     }
16875
16876     count = strlen(message);
16877     outCount = OutputToProcess(cps->pr, message, count, &error);
16878     if (outCount < count && !exiting
16879                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16880       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16881       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16882         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16883             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16884                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16885                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16886                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16887             } else {
16888                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16889                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16890                 gameInfo.result = res;
16891             }
16892             gameInfo.resultDetails = StrSave(buf);
16893         }
16894         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16895         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16896     }
16897 }
16898
16899 void
16900 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16901 {
16902     char *end_str;
16903     char buf[MSG_SIZ];
16904     ChessProgramState *cps = (ChessProgramState *)closure;
16905
16906     if (isr != cps->isr) return; /* Killed intentionally */
16907     if (count <= 0) {
16908         if (count == 0) {
16909             RemoveInputSource(cps->isr);
16910             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16911                     _(cps->which), cps->program);
16912             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16913             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16914                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16915                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16916                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16917                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16918                 } else {
16919                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16920                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16921                     gameInfo.result = res;
16922                 }
16923                 gameInfo.resultDetails = StrSave(buf);
16924             }
16925             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16926             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16927         } else {
16928             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16929                     _(cps->which), cps->program);
16930             RemoveInputSource(cps->isr);
16931
16932             /* [AS] Program is misbehaving badly... kill it */
16933             if( count == -2 ) {
16934                 DestroyChildProcess( cps->pr, 9 );
16935                 cps->pr = NoProc;
16936             }
16937
16938             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16939         }
16940         return;
16941     }
16942
16943     if ((end_str = strchr(message, '\r')) != NULL)
16944       *end_str = NULLCHAR;
16945     if ((end_str = strchr(message, '\n')) != NULL)
16946       *end_str = NULLCHAR;
16947
16948     if (appData.debugMode) {
16949         TimeMark now; int print = 1;
16950         char *quote = ""; char c; int i;
16951
16952         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16953                 char start = message[0];
16954                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16955                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16956                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16957                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16958                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16959                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16960                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16961                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16962                    sscanf(message, "hint: %c", &c)!=1 &&
16963                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16964                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16965                     print = (appData.engineComments >= 2);
16966                 }
16967                 message[0] = start; // restore original message
16968         }
16969         if(print) {
16970                 GetTimeMark(&now);
16971                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16972                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16973                         quote,
16974                         message);
16975                 if(serverFP)
16976                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16977                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16978                         quote,
16979                         message), fflush(serverFP);
16980         }
16981     }
16982
16983     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16984     if (appData.icsEngineAnalyze) {
16985         if (strstr(message, "whisper") != NULL ||
16986              strstr(message, "kibitz") != NULL ||
16987             strstr(message, "tellics") != NULL) return;
16988     }
16989
16990     HandleMachineMove(message, cps);
16991 }
16992
16993
16994 void
16995 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16996 {
16997     char buf[MSG_SIZ];
16998     int seconds;
16999
17000     if( timeControl_2 > 0 ) {
17001         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17002             tc = timeControl_2;
17003         }
17004     }
17005     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17006     inc /= cps->timeOdds;
17007     st  /= cps->timeOdds;
17008
17009     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17010
17011     if (st > 0) {
17012       /* Set exact time per move, normally using st command */
17013       if (cps->stKludge) {
17014         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17015         seconds = st % 60;
17016         if (seconds == 0) {
17017           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17018         } else {
17019           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17020         }
17021       } else {
17022         snprintf(buf, MSG_SIZ, "st %d\n", st);
17023       }
17024     } else {
17025       /* Set conventional or incremental time control, using level command */
17026       if (seconds == 0) {
17027         /* Note old gnuchess bug -- minutes:seconds used to not work.
17028            Fixed in later versions, but still avoid :seconds
17029            when seconds is 0. */
17030         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17031       } else {
17032         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17033                  seconds, inc/1000.);
17034       }
17035     }
17036     SendToProgram(buf, cps);
17037
17038     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17039     /* Orthogonally, limit search to given depth */
17040     if (sd > 0) {
17041       if (cps->sdKludge) {
17042         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17043       } else {
17044         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17045       }
17046       SendToProgram(buf, cps);
17047     }
17048
17049     if(cps->nps >= 0) { /* [HGM] nps */
17050         if(cps->supportsNPS == FALSE)
17051           cps->nps = -1; // don't use if engine explicitly says not supported!
17052         else {
17053           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17054           SendToProgram(buf, cps);
17055         }
17056     }
17057 }
17058
17059 ChessProgramState *
17060 WhitePlayer ()
17061 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17062 {
17063     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17064        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17065         return &second;
17066     return &first;
17067 }
17068
17069 void
17070 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17071 {
17072     char message[MSG_SIZ];
17073     long time, otime;
17074
17075     /* Note: this routine must be called when the clocks are stopped
17076        or when they have *just* been set or switched; otherwise
17077        it will be off by the time since the current tick started.
17078     */
17079     if (machineWhite) {
17080         time = whiteTimeRemaining / 10;
17081         otime = blackTimeRemaining / 10;
17082     } else {
17083         time = blackTimeRemaining / 10;
17084         otime = whiteTimeRemaining / 10;
17085     }
17086     /* [HGM] translate opponent's time by time-odds factor */
17087     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17088
17089     if (time <= 0) time = 1;
17090     if (otime <= 0) otime = 1;
17091
17092     snprintf(message, MSG_SIZ, "time %ld\n", time);
17093     SendToProgram(message, cps);
17094
17095     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17096     SendToProgram(message, cps);
17097 }
17098
17099 char *
17100 EngineDefinedVariant (ChessProgramState *cps, int n)
17101 {   // return name of n-th unknown variant that engine supports
17102     static char buf[MSG_SIZ];
17103     char *p, *s = cps->variants;
17104     if(!s) return NULL;
17105     do { // parse string from variants feature
17106       VariantClass v;
17107         p = strchr(s, ',');
17108         if(p) *p = NULLCHAR;
17109       v = StringToVariant(s);
17110       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17111         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17112             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17113                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17114                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17115                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17116             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17117         }
17118         if(p) *p++ = ',';
17119         if(n < 0) return buf;
17120     } while(s = p);
17121     return NULL;
17122 }
17123
17124 int
17125 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17126 {
17127   char buf[MSG_SIZ];
17128   int len = strlen(name);
17129   int val;
17130
17131   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17132     (*p) += len + 1;
17133     sscanf(*p, "%d", &val);
17134     *loc = (val != 0);
17135     while (**p && **p != ' ')
17136       (*p)++;
17137     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17138     SendToProgram(buf, cps);
17139     return TRUE;
17140   }
17141   return FALSE;
17142 }
17143
17144 int
17145 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17146 {
17147   char buf[MSG_SIZ];
17148   int len = strlen(name);
17149   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17150     (*p) += len + 1;
17151     sscanf(*p, "%d", loc);
17152     while (**p && **p != ' ') (*p)++;
17153     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17154     SendToProgram(buf, cps);
17155     return TRUE;
17156   }
17157   return FALSE;
17158 }
17159
17160 int
17161 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17162 {
17163   char buf[MSG_SIZ];
17164   int len = strlen(name);
17165   if (strncmp((*p), name, len) == 0
17166       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17167     (*p) += len + 2;
17168     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17169     sscanf(*p, "%[^\"]", *loc);
17170     while (**p && **p != '\"') (*p)++;
17171     if (**p == '\"') (*p)++;
17172     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17173     SendToProgram(buf, cps);
17174     return TRUE;
17175   }
17176   return FALSE;
17177 }
17178
17179 int
17180 ParseOption (Option *opt, ChessProgramState *cps)
17181 // [HGM] options: process the string that defines an engine option, and determine
17182 // name, type, default value, and allowed value range
17183 {
17184         char *p, *q, buf[MSG_SIZ];
17185         int n, min = (-1)<<31, max = 1<<31, def;
17186
17187         opt->target = &opt->value;   // OK for spin/slider and checkbox
17188         if(p = strstr(opt->name, " -spin ")) {
17189             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17190             if(max < min) max = min; // enforce consistency
17191             if(def < min) def = min;
17192             if(def > max) def = max;
17193             opt->value = def;
17194             opt->min = min;
17195             opt->max = max;
17196             opt->type = Spin;
17197         } else if((p = strstr(opt->name, " -slider "))) {
17198             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17199             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17200             if(max < min) max = min; // enforce consistency
17201             if(def < min) def = min;
17202             if(def > max) def = max;
17203             opt->value = def;
17204             opt->min = min;
17205             opt->max = max;
17206             opt->type = Spin; // Slider;
17207         } else if((p = strstr(opt->name, " -string "))) {
17208             opt->textValue = p+9;
17209             opt->type = TextBox;
17210             opt->target = &opt->textValue;
17211         } else if((p = strstr(opt->name, " -file "))) {
17212             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17213             opt->target = opt->textValue = p+7;
17214             opt->type = FileName; // FileName;
17215             opt->target = &opt->textValue;
17216         } else if((p = strstr(opt->name, " -path "))) {
17217             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17218             opt->target = opt->textValue = p+7;
17219             opt->type = PathName; // PathName;
17220             opt->target = &opt->textValue;
17221         } else if(p = strstr(opt->name, " -check ")) {
17222             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17223             opt->value = (def != 0);
17224             opt->type = CheckBox;
17225         } else if(p = strstr(opt->name, " -combo ")) {
17226             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17227             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17228             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17229             opt->value = n = 0;
17230             while(q = StrStr(q, " /// ")) {
17231                 n++; *q = 0;    // count choices, and null-terminate each of them
17232                 q += 5;
17233                 if(*q == '*') { // remember default, which is marked with * prefix
17234                     q++;
17235                     opt->value = n;
17236                 }
17237                 cps->comboList[cps->comboCnt++] = q;
17238             }
17239             cps->comboList[cps->comboCnt++] = NULL;
17240             opt->max = n + 1;
17241             opt->type = ComboBox;
17242         } else if(p = strstr(opt->name, " -button")) {
17243             opt->type = Button;
17244         } else if(p = strstr(opt->name, " -save")) {
17245             opt->type = SaveButton;
17246         } else return FALSE;
17247         *p = 0; // terminate option name
17248         // now look if the command-line options define a setting for this engine option.
17249         if(cps->optionSettings && cps->optionSettings[0])
17250             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17251         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17252           snprintf(buf, MSG_SIZ, "option %s", p);
17253                 if(p = strstr(buf, ",")) *p = 0;
17254                 if(q = strchr(buf, '=')) switch(opt->type) {
17255                     case ComboBox:
17256                         for(n=0; n<opt->max; n++)
17257                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17258                         break;
17259                     case TextBox:
17260                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17261                         break;
17262                     case Spin:
17263                     case CheckBox:
17264                         opt->value = atoi(q+1);
17265                     default:
17266                         break;
17267                 }
17268                 strcat(buf, "\n");
17269                 SendToProgram(buf, cps);
17270         }
17271         return TRUE;
17272 }
17273
17274 void
17275 FeatureDone (ChessProgramState *cps, int val)
17276 {
17277   DelayedEventCallback cb = GetDelayedEvent();
17278   if ((cb == InitBackEnd3 && cps == &first) ||
17279       (cb == SettingsMenuIfReady && cps == &second) ||
17280       (cb == LoadEngine) ||
17281       (cb == TwoMachinesEventIfReady)) {
17282     CancelDelayedEvent();
17283     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17284   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17285   cps->initDone = val;
17286   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17287 }
17288
17289 /* Parse feature command from engine */
17290 void
17291 ParseFeatures (char *args, ChessProgramState *cps)
17292 {
17293   char *p = args;
17294   char *q = NULL;
17295   int val;
17296   char buf[MSG_SIZ];
17297
17298   for (;;) {
17299     while (*p == ' ') p++;
17300     if (*p == NULLCHAR) return;
17301
17302     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17303     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17304     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17305     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17306     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17307     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17308     if (BoolFeature(&p, "reuse", &val, cps)) {
17309       /* Engine can disable reuse, but can't enable it if user said no */
17310       if (!val) cps->reuse = FALSE;
17311       continue;
17312     }
17313     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17314     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17315       if (gameMode == TwoMachinesPlay) {
17316         DisplayTwoMachinesTitle();
17317       } else {
17318         DisplayTitle("");
17319       }
17320       continue;
17321     }
17322     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17323     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17324     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17325     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17326     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17327     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17328     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17329     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17330     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17331     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17332     if (IntFeature(&p, "done", &val, cps)) {
17333       FeatureDone(cps, val);
17334       continue;
17335     }
17336     /* Added by Tord: */
17337     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17338     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17339     /* End of additions by Tord */
17340
17341     /* [HGM] added features: */
17342     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17343     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17344     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17345     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17346     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17347     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17348     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17349     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17350         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17351         FREE(cps->option[cps->nrOptions].name);
17352         cps->option[cps->nrOptions].name = q; q = NULL;
17353         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17354           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17355             SendToProgram(buf, cps);
17356             continue;
17357         }
17358         if(cps->nrOptions >= MAX_OPTIONS) {
17359             cps->nrOptions--;
17360             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17361             DisplayError(buf, 0);
17362         }
17363         continue;
17364     }
17365     /* End of additions by HGM */
17366
17367     /* unknown feature: complain and skip */
17368     q = p;
17369     while (*q && *q != '=') q++;
17370     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17371     SendToProgram(buf, cps);
17372     p = q;
17373     if (*p == '=') {
17374       p++;
17375       if (*p == '\"') {
17376         p++;
17377         while (*p && *p != '\"') p++;
17378         if (*p == '\"') p++;
17379       } else {
17380         while (*p && *p != ' ') p++;
17381       }
17382     }
17383   }
17384
17385 }
17386
17387 void
17388 PeriodicUpdatesEvent (int newState)
17389 {
17390     if (newState == appData.periodicUpdates)
17391       return;
17392
17393     appData.periodicUpdates=newState;
17394
17395     /* Display type changes, so update it now */
17396 //    DisplayAnalysis();
17397
17398     /* Get the ball rolling again... */
17399     if (newState) {
17400         AnalysisPeriodicEvent(1);
17401         StartAnalysisClock();
17402     }
17403 }
17404
17405 void
17406 PonderNextMoveEvent (int newState)
17407 {
17408     if (newState == appData.ponderNextMove) return;
17409     if (gameMode == EditPosition) EditPositionDone(TRUE);
17410     if (newState) {
17411         SendToProgram("hard\n", &first);
17412         if (gameMode == TwoMachinesPlay) {
17413             SendToProgram("hard\n", &second);
17414         }
17415     } else {
17416         SendToProgram("easy\n", &first);
17417         thinkOutput[0] = NULLCHAR;
17418         if (gameMode == TwoMachinesPlay) {
17419             SendToProgram("easy\n", &second);
17420         }
17421     }
17422     appData.ponderNextMove = newState;
17423 }
17424
17425 void
17426 NewSettingEvent (int option, int *feature, char *command, int value)
17427 {
17428     char buf[MSG_SIZ];
17429
17430     if (gameMode == EditPosition) EditPositionDone(TRUE);
17431     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17432     if(feature == NULL || *feature) SendToProgram(buf, &first);
17433     if (gameMode == TwoMachinesPlay) {
17434         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17435     }
17436 }
17437
17438 void
17439 ShowThinkingEvent ()
17440 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17441 {
17442     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17443     int newState = appData.showThinking
17444         // [HGM] thinking: other features now need thinking output as well
17445         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17446
17447     if (oldState == newState) return;
17448     oldState = newState;
17449     if (gameMode == EditPosition) EditPositionDone(TRUE);
17450     if (oldState) {
17451         SendToProgram("post\n", &first);
17452         if (gameMode == TwoMachinesPlay) {
17453             SendToProgram("post\n", &second);
17454         }
17455     } else {
17456         SendToProgram("nopost\n", &first);
17457         thinkOutput[0] = NULLCHAR;
17458         if (gameMode == TwoMachinesPlay) {
17459             SendToProgram("nopost\n", &second);
17460         }
17461     }
17462 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17463 }
17464
17465 void
17466 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17467 {
17468   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17469   if (pr == NoProc) return;
17470   AskQuestion(title, question, replyPrefix, pr);
17471 }
17472
17473 void
17474 TypeInEvent (char firstChar)
17475 {
17476     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17477         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17478         gameMode == AnalyzeMode || gameMode == EditGame ||
17479         gameMode == EditPosition || gameMode == IcsExamining ||
17480         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17481         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17482                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17483                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17484         gameMode == Training) PopUpMoveDialog(firstChar);
17485 }
17486
17487 void
17488 TypeInDoneEvent (char *move)
17489 {
17490         Board board;
17491         int n, fromX, fromY, toX, toY;
17492         char promoChar;
17493         ChessMove moveType;
17494
17495         // [HGM] FENedit
17496         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17497                 EditPositionPasteFEN(move);
17498                 return;
17499         }
17500         // [HGM] movenum: allow move number to be typed in any mode
17501         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17502           ToNrEvent(2*n-1);
17503           return;
17504         }
17505         // undocumented kludge: allow command-line option to be typed in!
17506         // (potentially fatal, and does not implement the effect of the option.)
17507         // should only be used for options that are values on which future decisions will be made,
17508         // and definitely not on options that would be used during initialization.
17509         if(strstr(move, "!!! -") == move) {
17510             ParseArgsFromString(move+4);
17511             return;
17512         }
17513
17514       if (gameMode != EditGame && currentMove != forwardMostMove &&
17515         gameMode != Training) {
17516         DisplayMoveError(_("Displayed move is not current"));
17517       } else {
17518         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17519           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17520         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17521         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17522           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17523           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17524         } else {
17525           DisplayMoveError(_("Could not parse move"));
17526         }
17527       }
17528 }
17529
17530 void
17531 DisplayMove (int moveNumber)
17532 {
17533     char message[MSG_SIZ];
17534     char res[MSG_SIZ];
17535     char cpThinkOutput[MSG_SIZ];
17536
17537     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17538
17539     if (moveNumber == forwardMostMove - 1 ||
17540         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17541
17542         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17543
17544         if (strchr(cpThinkOutput, '\n')) {
17545             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17546         }
17547     } else {
17548         *cpThinkOutput = NULLCHAR;
17549     }
17550
17551     /* [AS] Hide thinking from human user */
17552     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17553         *cpThinkOutput = NULLCHAR;
17554         if( thinkOutput[0] != NULLCHAR ) {
17555             int i;
17556
17557             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17558                 cpThinkOutput[i] = '.';
17559             }
17560             cpThinkOutput[i] = NULLCHAR;
17561             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17562         }
17563     }
17564
17565     if (moveNumber == forwardMostMove - 1 &&
17566         gameInfo.resultDetails != NULL) {
17567         if (gameInfo.resultDetails[0] == NULLCHAR) {
17568           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17569         } else {
17570           snprintf(res, MSG_SIZ, " {%s} %s",
17571                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17572         }
17573     } else {
17574         res[0] = NULLCHAR;
17575     }
17576
17577     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17578         DisplayMessage(res, cpThinkOutput);
17579     } else {
17580       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17581                 WhiteOnMove(moveNumber) ? " " : ".. ",
17582                 parseList[moveNumber], res);
17583         DisplayMessage(message, cpThinkOutput);
17584     }
17585 }
17586
17587 void
17588 DisplayComment (int moveNumber, char *text)
17589 {
17590     char title[MSG_SIZ];
17591
17592     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17593       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17594     } else {
17595       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17596               WhiteOnMove(moveNumber) ? " " : ".. ",
17597               parseList[moveNumber]);
17598     }
17599     if (text != NULL && (appData.autoDisplayComment || commentUp))
17600         CommentPopUp(title, text);
17601 }
17602
17603 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17604  * might be busy thinking or pondering.  It can be omitted if your
17605  * gnuchess is configured to stop thinking immediately on any user
17606  * input.  However, that gnuchess feature depends on the FIONREAD
17607  * ioctl, which does not work properly on some flavors of Unix.
17608  */
17609 void
17610 Attention (ChessProgramState *cps)
17611 {
17612 #if ATTENTION
17613     if (!cps->useSigint) return;
17614     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17615     switch (gameMode) {
17616       case MachinePlaysWhite:
17617       case MachinePlaysBlack:
17618       case TwoMachinesPlay:
17619       case IcsPlayingWhite:
17620       case IcsPlayingBlack:
17621       case AnalyzeMode:
17622       case AnalyzeFile:
17623         /* Skip if we know it isn't thinking */
17624         if (!cps->maybeThinking) return;
17625         if (appData.debugMode)
17626           fprintf(debugFP, "Interrupting %s\n", cps->which);
17627         InterruptChildProcess(cps->pr);
17628         cps->maybeThinking = FALSE;
17629         break;
17630       default:
17631         break;
17632     }
17633 #endif /*ATTENTION*/
17634 }
17635
17636 int
17637 CheckFlags ()
17638 {
17639     if (whiteTimeRemaining <= 0) {
17640         if (!whiteFlag) {
17641             whiteFlag = TRUE;
17642             if (appData.icsActive) {
17643                 if (appData.autoCallFlag &&
17644                     gameMode == IcsPlayingBlack && !blackFlag) {
17645                   SendToICS(ics_prefix);
17646                   SendToICS("flag\n");
17647                 }
17648             } else {
17649                 if (blackFlag) {
17650                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17651                 } else {
17652                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17653                     if (appData.autoCallFlag) {
17654                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17655                         return TRUE;
17656                     }
17657                 }
17658             }
17659         }
17660     }
17661     if (blackTimeRemaining <= 0) {
17662         if (!blackFlag) {
17663             blackFlag = TRUE;
17664             if (appData.icsActive) {
17665                 if (appData.autoCallFlag &&
17666                     gameMode == IcsPlayingWhite && !whiteFlag) {
17667                   SendToICS(ics_prefix);
17668                   SendToICS("flag\n");
17669                 }
17670             } else {
17671                 if (whiteFlag) {
17672                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17673                 } else {
17674                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17675                     if (appData.autoCallFlag) {
17676                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17677                         return TRUE;
17678                     }
17679                 }
17680             }
17681         }
17682     }
17683     return FALSE;
17684 }
17685
17686 void
17687 CheckTimeControl ()
17688 {
17689     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17690         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17691
17692     /*
17693      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17694      */
17695     if ( !WhiteOnMove(forwardMostMove) ) {
17696         /* White made time control */
17697         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17698         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17699         /* [HGM] time odds: correct new time quota for time odds! */
17700                                             / WhitePlayer()->timeOdds;
17701         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17702     } else {
17703         lastBlack -= blackTimeRemaining;
17704         /* Black made time control */
17705         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17706                                             / WhitePlayer()->other->timeOdds;
17707         lastWhite = whiteTimeRemaining;
17708     }
17709 }
17710
17711 void
17712 DisplayBothClocks ()
17713 {
17714     int wom = gameMode == EditPosition ?
17715       !blackPlaysFirst : WhiteOnMove(currentMove);
17716     DisplayWhiteClock(whiteTimeRemaining, wom);
17717     DisplayBlackClock(blackTimeRemaining, !wom);
17718 }
17719
17720
17721 /* Timekeeping seems to be a portability nightmare.  I think everyone
17722    has ftime(), but I'm really not sure, so I'm including some ifdefs
17723    to use other calls if you don't.  Clocks will be less accurate if
17724    you have neither ftime nor gettimeofday.
17725 */
17726
17727 /* VS 2008 requires the #include outside of the function */
17728 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17729 #include <sys/timeb.h>
17730 #endif
17731
17732 /* Get the current time as a TimeMark */
17733 void
17734 GetTimeMark (TimeMark *tm)
17735 {
17736 #if HAVE_GETTIMEOFDAY
17737
17738     struct timeval timeVal;
17739     struct timezone timeZone;
17740
17741     gettimeofday(&timeVal, &timeZone);
17742     tm->sec = (long) timeVal.tv_sec;
17743     tm->ms = (int) (timeVal.tv_usec / 1000L);
17744
17745 #else /*!HAVE_GETTIMEOFDAY*/
17746 #if HAVE_FTIME
17747
17748 // include <sys/timeb.h> / moved to just above start of function
17749     struct timeb timeB;
17750
17751     ftime(&timeB);
17752     tm->sec = (long) timeB.time;
17753     tm->ms = (int) timeB.millitm;
17754
17755 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17756     tm->sec = (long) time(NULL);
17757     tm->ms = 0;
17758 #endif
17759 #endif
17760 }
17761
17762 /* Return the difference in milliseconds between two
17763    time marks.  We assume the difference will fit in a long!
17764 */
17765 long
17766 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17767 {
17768     return 1000L*(tm2->sec - tm1->sec) +
17769            (long) (tm2->ms - tm1->ms);
17770 }
17771
17772
17773 /*
17774  * Code to manage the game clocks.
17775  *
17776  * In tournament play, black starts the clock and then white makes a move.
17777  * We give the human user a slight advantage if he is playing white---the
17778  * clocks don't run until he makes his first move, so it takes zero time.
17779  * Also, we don't account for network lag, so we could get out of sync
17780  * with GNU Chess's clock -- but then, referees are always right.
17781  */
17782
17783 static TimeMark tickStartTM;
17784 static long intendedTickLength;
17785
17786 long
17787 NextTickLength (long timeRemaining)
17788 {
17789     long nominalTickLength, nextTickLength;
17790
17791     if (timeRemaining > 0L && timeRemaining <= 10000L)
17792       nominalTickLength = 100L;
17793     else
17794       nominalTickLength = 1000L;
17795     nextTickLength = timeRemaining % nominalTickLength;
17796     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17797
17798     return nextTickLength;
17799 }
17800
17801 /* Adjust clock one minute up or down */
17802 void
17803 AdjustClock (Boolean which, int dir)
17804 {
17805     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17806     if(which) blackTimeRemaining += 60000*dir;
17807     else      whiteTimeRemaining += 60000*dir;
17808     DisplayBothClocks();
17809     adjustedClock = TRUE;
17810 }
17811
17812 /* Stop clocks and reset to a fresh time control */
17813 void
17814 ResetClocks ()
17815 {
17816     (void) StopClockTimer();
17817     if (appData.icsActive) {
17818         whiteTimeRemaining = blackTimeRemaining = 0;
17819     } else if (searchTime) {
17820         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17821         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17822     } else { /* [HGM] correct new time quote for time odds */
17823         whiteTC = blackTC = fullTimeControlString;
17824         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17825         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17826     }
17827     if (whiteFlag || blackFlag) {
17828         DisplayTitle("");
17829         whiteFlag = blackFlag = FALSE;
17830     }
17831     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17832     DisplayBothClocks();
17833     adjustedClock = FALSE;
17834 }
17835
17836 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17837
17838 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17839
17840 /* Decrement running clock by amount of time that has passed */
17841 void
17842 DecrementClocks ()
17843 {
17844     long tRemaining;
17845     long lastTickLength, fudge;
17846     TimeMark now;
17847
17848     if (!appData.clockMode) return;
17849     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17850
17851     GetTimeMark(&now);
17852
17853     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17854
17855     /* Fudge if we woke up a little too soon */
17856     fudge = intendedTickLength - lastTickLength;
17857     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17858
17859     if (WhiteOnMove(forwardMostMove)) {
17860         if(whiteNPS >= 0) lastTickLength = 0;
17861          tRemaining = whiteTimeRemaining -= lastTickLength;
17862         if( tRemaining < 0 && !appData.icsActive) {
17863             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17864             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17865                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17866                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17867             }
17868         }
17869         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17870         DisplayWhiteClock(whiteTimeRemaining - fudge,
17871                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17872         timeSuffix = 0;
17873     } else {
17874         if(blackNPS >= 0) lastTickLength = 0;
17875          tRemaining = blackTimeRemaining -= lastTickLength;
17876         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17877             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17878             if(suddenDeath) {
17879                 blackStartMove = forwardMostMove;
17880                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17881             }
17882         }
17883         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17884         DisplayBlackClock(blackTimeRemaining - fudge,
17885                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17886         timeSuffix = 0;
17887     }
17888     if (CheckFlags()) return;
17889
17890     if(twoBoards) { // count down secondary board's clocks as well
17891         activePartnerTime -= lastTickLength;
17892         partnerUp = 1;
17893         if(activePartner == 'W')
17894             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17895         else
17896             DisplayBlackClock(activePartnerTime, TRUE);
17897         partnerUp = 0;
17898     }
17899
17900     tickStartTM = now;
17901     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17902     StartClockTimer(intendedTickLength);
17903
17904     /* if the time remaining has fallen below the alarm threshold, sound the
17905      * alarm. if the alarm has sounded and (due to a takeback or time control
17906      * with increment) the time remaining has increased to a level above the
17907      * threshold, reset the alarm so it can sound again.
17908      */
17909
17910     if (appData.icsActive && appData.icsAlarm) {
17911
17912         /* make sure we are dealing with the user's clock */
17913         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17914                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17915            )) return;
17916
17917         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17918             alarmSounded = FALSE;
17919         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17920             PlayAlarmSound();
17921             alarmSounded = TRUE;
17922         }
17923     }
17924 }
17925
17926
17927 /* A player has just moved, so stop the previously running
17928    clock and (if in clock mode) start the other one.
17929    We redisplay both clocks in case we're in ICS mode, because
17930    ICS gives us an update to both clocks after every move.
17931    Note that this routine is called *after* forwardMostMove
17932    is updated, so the last fractional tick must be subtracted
17933    from the color that is *not* on move now.
17934 */
17935 void
17936 SwitchClocks (int newMoveNr)
17937 {
17938     long lastTickLength;
17939     TimeMark now;
17940     int flagged = FALSE;
17941
17942     GetTimeMark(&now);
17943
17944     if (StopClockTimer() && appData.clockMode) {
17945         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17946         if (!WhiteOnMove(forwardMostMove)) {
17947             if(blackNPS >= 0) lastTickLength = 0;
17948             blackTimeRemaining -= lastTickLength;
17949            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17950 //         if(pvInfoList[forwardMostMove].time == -1)
17951                  pvInfoList[forwardMostMove].time =               // use GUI time
17952                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17953         } else {
17954            if(whiteNPS >= 0) lastTickLength = 0;
17955            whiteTimeRemaining -= lastTickLength;
17956            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17957 //         if(pvInfoList[forwardMostMove].time == -1)
17958                  pvInfoList[forwardMostMove].time =
17959                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17960         }
17961         flagged = CheckFlags();
17962     }
17963     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17964     CheckTimeControl();
17965
17966     if (flagged || !appData.clockMode) return;
17967
17968     switch (gameMode) {
17969       case MachinePlaysBlack:
17970       case MachinePlaysWhite:
17971       case BeginningOfGame:
17972         if (pausing) return;
17973         break;
17974
17975       case EditGame:
17976       case PlayFromGameFile:
17977       case IcsExamining:
17978         return;
17979
17980       default:
17981         break;
17982     }
17983
17984     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17985         if(WhiteOnMove(forwardMostMove))
17986              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17987         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17988     }
17989
17990     tickStartTM = now;
17991     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17992       whiteTimeRemaining : blackTimeRemaining);
17993     StartClockTimer(intendedTickLength);
17994 }
17995
17996
17997 /* Stop both clocks */
17998 void
17999 StopClocks ()
18000 {
18001     long lastTickLength;
18002     TimeMark now;
18003
18004     if (!StopClockTimer()) return;
18005     if (!appData.clockMode) return;
18006
18007     GetTimeMark(&now);
18008
18009     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18010     if (WhiteOnMove(forwardMostMove)) {
18011         if(whiteNPS >= 0) lastTickLength = 0;
18012         whiteTimeRemaining -= lastTickLength;
18013         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18014     } else {
18015         if(blackNPS >= 0) lastTickLength = 0;
18016         blackTimeRemaining -= lastTickLength;
18017         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18018     }
18019     CheckFlags();
18020 }
18021
18022 /* Start clock of player on move.  Time may have been reset, so
18023    if clock is already running, stop and restart it. */
18024 void
18025 StartClocks ()
18026 {
18027     (void) StopClockTimer(); /* in case it was running already */
18028     DisplayBothClocks();
18029     if (CheckFlags()) return;
18030
18031     if (!appData.clockMode) return;
18032     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18033
18034     GetTimeMark(&tickStartTM);
18035     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18036       whiteTimeRemaining : blackTimeRemaining);
18037
18038    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18039     whiteNPS = blackNPS = -1;
18040     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18041        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18042         whiteNPS = first.nps;
18043     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18044        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18045         blackNPS = first.nps;
18046     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18047         whiteNPS = second.nps;
18048     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18049         blackNPS = second.nps;
18050     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18051
18052     StartClockTimer(intendedTickLength);
18053 }
18054
18055 char *
18056 TimeString (long ms)
18057 {
18058     long second, minute, hour, day;
18059     char *sign = "";
18060     static char buf[40], moveTime[8];
18061
18062     if (ms > 0 && ms <= 9900) {
18063       /* convert milliseconds to tenths, rounding up */
18064       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18065
18066       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18067       return buf;
18068     }
18069
18070     /* convert milliseconds to seconds, rounding up */
18071     /* use floating point to avoid strangeness of integer division
18072        with negative dividends on many machines */
18073     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18074
18075     if (second < 0) {
18076         sign = "-";
18077         second = -second;
18078     }
18079
18080     day = second / (60 * 60 * 24);
18081     second = second % (60 * 60 * 24);
18082     hour = second / (60 * 60);
18083     second = second % (60 * 60);
18084     minute = second / 60;
18085     second = second % 60;
18086
18087     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18088     else *moveTime = NULLCHAR;
18089
18090     if (day > 0)
18091       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18092               sign, day, hour, minute, second, moveTime);
18093     else if (hour > 0)
18094       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18095     else
18096       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18097
18098     return buf;
18099 }
18100
18101
18102 /*
18103  * This is necessary because some C libraries aren't ANSI C compliant yet.
18104  */
18105 char *
18106 StrStr (char *string, char *match)
18107 {
18108     int i, length;
18109
18110     length = strlen(match);
18111
18112     for (i = strlen(string) - length; i >= 0; i--, string++)
18113       if (!strncmp(match, string, length))
18114         return string;
18115
18116     return NULL;
18117 }
18118
18119 char *
18120 StrCaseStr (char *string, char *match)
18121 {
18122     int i, j, length;
18123
18124     length = strlen(match);
18125
18126     for (i = strlen(string) - length; i >= 0; i--, string++) {
18127         for (j = 0; j < length; j++) {
18128             if (ToLower(match[j]) != ToLower(string[j]))
18129               break;
18130         }
18131         if (j == length) return string;
18132     }
18133
18134     return NULL;
18135 }
18136
18137 #ifndef _amigados
18138 int
18139 StrCaseCmp (char *s1, char *s2)
18140 {
18141     char c1, c2;
18142
18143     for (;;) {
18144         c1 = ToLower(*s1++);
18145         c2 = ToLower(*s2++);
18146         if (c1 > c2) return 1;
18147         if (c1 < c2) return -1;
18148         if (c1 == NULLCHAR) return 0;
18149     }
18150 }
18151
18152
18153 int
18154 ToLower (int c)
18155 {
18156     return isupper(c) ? tolower(c) : c;
18157 }
18158
18159
18160 int
18161 ToUpper (int c)
18162 {
18163     return islower(c) ? toupper(c) : c;
18164 }
18165 #endif /* !_amigados    */
18166
18167 char *
18168 StrSave (char *s)
18169 {
18170   char *ret;
18171
18172   if ((ret = (char *) malloc(strlen(s) + 1)))
18173     {
18174       safeStrCpy(ret, s, strlen(s)+1);
18175     }
18176   return ret;
18177 }
18178
18179 char *
18180 StrSavePtr (char *s, char **savePtr)
18181 {
18182     if (*savePtr) {
18183         free(*savePtr);
18184     }
18185     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18186       safeStrCpy(*savePtr, s, strlen(s)+1);
18187     }
18188     return(*savePtr);
18189 }
18190
18191 char *
18192 PGNDate ()
18193 {
18194     time_t clock;
18195     struct tm *tm;
18196     char buf[MSG_SIZ];
18197
18198     clock = time((time_t *)NULL);
18199     tm = localtime(&clock);
18200     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18201             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18202     return StrSave(buf);
18203 }
18204
18205
18206 char *
18207 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18208 {
18209     int i, j, fromX, fromY, toX, toY;
18210     int whiteToPlay, haveRights = nrCastlingRights;
18211     char buf[MSG_SIZ];
18212     char *p, *q;
18213     int emptycount;
18214     ChessSquare piece;
18215
18216     whiteToPlay = (gameMode == EditPosition) ?
18217       !blackPlaysFirst : (move % 2 == 0);
18218     p = buf;
18219
18220     /* Piece placement data */
18221     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18222         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18223         emptycount = 0;
18224         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18225             if (boards[move][i][j] == EmptySquare) {
18226                 emptycount++;
18227             } else { ChessSquare piece = boards[move][i][j];
18228                 if (emptycount > 0) {
18229                     if(emptycount<10) /* [HGM] can be >= 10 */
18230                         *p++ = '0' + emptycount;
18231                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18232                     emptycount = 0;
18233                 }
18234                 if(PieceToChar(piece) == '+') {
18235                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18236                     *p++ = '+';
18237                     piece = (ChessSquare)(CHUDEMOTED(piece));
18238                 }
18239                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18240                 if(*p = PieceSuffix(piece)) p++;
18241                 if(p[-1] == '~') {
18242                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18243                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18244                     *p++ = '~';
18245                 }
18246             }
18247         }
18248         if (emptycount > 0) {
18249             if(emptycount<10) /* [HGM] can be >= 10 */
18250                 *p++ = '0' + emptycount;
18251             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18252             emptycount = 0;
18253         }
18254         *p++ = '/';
18255     }
18256     *(p - 1) = ' ';
18257
18258     /* [HGM] print Crazyhouse or Shogi holdings */
18259     if( gameInfo.holdingsWidth ) {
18260         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18261         q = p;
18262         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18263             piece = boards[move][i][BOARD_WIDTH-1];
18264             if( piece != EmptySquare )
18265               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18266                   *p++ = PieceToChar(piece);
18267         }
18268         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18269             piece = boards[move][BOARD_HEIGHT-i-1][0];
18270             if( piece != EmptySquare )
18271               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18272                   *p++ = PieceToChar(piece);
18273         }
18274
18275         if( q == p ) *p++ = '-';
18276         *p++ = ']';
18277         *p++ = ' ';
18278     }
18279
18280     /* Active color */
18281     *p++ = whiteToPlay ? 'w' : 'b';
18282     *p++ = ' ';
18283
18284   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18285     haveRights = 0; q = p;
18286     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18287       piece = boards[move][0][i];
18288       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18289         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18290       }
18291     }
18292     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18293       piece = boards[move][BOARD_HEIGHT-1][i];
18294       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18295         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18296       }
18297     }
18298     if(p == q) *p++ = '-';
18299     *p++ = ' ';
18300   }
18301
18302   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18303     while(*p++ = *q++)
18304                       ;
18305     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18306   } else {
18307   if(haveRights) {
18308      int handW=0, handB=0;
18309      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18310         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18311         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18312      }
18313      q = p;
18314      if(appData.fischerCastling) {
18315         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18316            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18317                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18318         } else {
18319        /* [HGM] write directly from rights */
18320            if(boards[move][CASTLING][2] != NoRights &&
18321               boards[move][CASTLING][0] != NoRights   )
18322                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18323            if(boards[move][CASTLING][2] != NoRights &&
18324               boards[move][CASTLING][1] != NoRights   )
18325                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18326         }
18327         if(handB) {
18328            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18329                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18330         } else {
18331            if(boards[move][CASTLING][5] != NoRights &&
18332               boards[move][CASTLING][3] != NoRights   )
18333                 *p++ = boards[move][CASTLING][3] + AAA;
18334            if(boards[move][CASTLING][5] != NoRights &&
18335               boards[move][CASTLING][4] != NoRights   )
18336                 *p++ = boards[move][CASTLING][4] + AAA;
18337         }
18338      } else {
18339
18340         /* [HGM] write true castling rights */
18341         if( nrCastlingRights == 6 ) {
18342             int q, k=0;
18343             if(boards[move][CASTLING][0] != NoRights &&
18344                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18345             q = (boards[move][CASTLING][1] != NoRights &&
18346                  boards[move][CASTLING][2] != NoRights  );
18347             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18348                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18349                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18350                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18351             }
18352             if(q) *p++ = 'Q';
18353             k = 0;
18354             if(boards[move][CASTLING][3] != NoRights &&
18355                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18356             q = (boards[move][CASTLING][4] != NoRights &&
18357                  boards[move][CASTLING][5] != NoRights  );
18358             if(handB) {
18359                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18360                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18361                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18362             }
18363             if(q) *p++ = 'q';
18364         }
18365      }
18366      if (q == p) *p++ = '-'; /* No castling rights */
18367      *p++ = ' ';
18368   }
18369
18370   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18371      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18372      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18373     /* En passant target square */
18374     if (move > backwardMostMove) {
18375         fromX = moveList[move - 1][0] - AAA;
18376         fromY = moveList[move - 1][1] - ONE;
18377         toX = moveList[move - 1][2] - AAA;
18378         toY = moveList[move - 1][3] - ONE;
18379         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18380             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18381             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18382             fromX == toX) {
18383             /* 2-square pawn move just happened */
18384             *p++ = toX + AAA;
18385             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18386         } else {
18387             *p++ = '-';
18388         }
18389     } else if(move == backwardMostMove) {
18390         // [HGM] perhaps we should always do it like this, and forget the above?
18391         if((signed char)boards[move][EP_STATUS] >= 0) {
18392             *p++ = boards[move][EP_STATUS] + AAA;
18393             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18394         } else {
18395             *p++ = '-';
18396         }
18397     } else {
18398         *p++ = '-';
18399     }
18400     *p++ = ' ';
18401   }
18402   }
18403
18404     if(moveCounts)
18405     {   int i = 0, j=move;
18406
18407         /* [HGM] find reversible plies */
18408         if (appData.debugMode) { int k;
18409             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18410             for(k=backwardMostMove; k<=forwardMostMove; k++)
18411                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18412
18413         }
18414
18415         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18416         if( j == backwardMostMove ) i += initialRulePlies;
18417         sprintf(p, "%d ", i);
18418         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18419
18420         /* Fullmove number */
18421         sprintf(p, "%d", (move / 2) + 1);
18422     } else *--p = NULLCHAR;
18423
18424     return StrSave(buf);
18425 }
18426
18427 Boolean
18428 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18429 {
18430     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18431     char *p, c;
18432     int emptycount, virgin[BOARD_FILES];
18433     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18434
18435     p = fen;
18436
18437     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18438
18439     /* Piece placement data */
18440     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18441         j = 0;
18442         for (;;) {
18443             if (*p == '/' || *p == ' ' || *p == '[' ) {
18444                 if(j > w) w = j;
18445                 emptycount = gameInfo.boardWidth - j;
18446                 while (emptycount--)
18447                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18448                 if (*p == '/') p++;
18449                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18450                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18451                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18452                     }
18453                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18454                 }
18455                 break;
18456 #if(BOARD_FILES >= 10)*0
18457             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18458                 p++; emptycount=10;
18459                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18460                 while (emptycount--)
18461                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18462 #endif
18463             } else if (*p == '*') {
18464                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18465             } else if (isdigit(*p)) {
18466                 emptycount = *p++ - '0';
18467                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18468                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18469                 while (emptycount--)
18470                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18471             } else if (*p == '<') {
18472                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18473                 else if (i != 0 || !shuffle) return FALSE;
18474                 p++;
18475             } else if (shuffle && *p == '>') {
18476                 p++; // for now ignore closing shuffle range, and assume rank-end
18477             } else if (*p == '?') {
18478                 if (j >= gameInfo.boardWidth) return FALSE;
18479                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18480                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18481             } else if (*p == '+' || isalpha(*p)) {
18482                 char *q, *s = SUFFIXES;
18483                 if (j >= gameInfo.boardWidth) return FALSE;
18484                 if(*p=='+') {
18485                     char c = *++p;
18486                     if(q = strchr(s, p[1])) p++;
18487                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18488                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18489                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18490                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18491                 } else {
18492                     char c = *p++;
18493                     if(q = strchr(s, *p)) p++;
18494                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18495                 }
18496
18497                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18498                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18499                     piece = (ChessSquare) (PROMOTED(piece));
18500                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18501                     p++;
18502                 }
18503                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18504                 if(piece == king) wKingRank = i;
18505                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18506             } else {
18507                 return FALSE;
18508             }
18509         }
18510     }
18511     while (*p == '/' || *p == ' ') p++;
18512
18513     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18514
18515     /* [HGM] by default clear Crazyhouse holdings, if present */
18516     if(gameInfo.holdingsWidth) {
18517        for(i=0; i<BOARD_HEIGHT; i++) {
18518            board[i][0]             = EmptySquare; /* black holdings */
18519            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18520            board[i][1]             = (ChessSquare) 0; /* black counts */
18521            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18522        }
18523     }
18524
18525     /* [HGM] look for Crazyhouse holdings here */
18526     while(*p==' ') p++;
18527     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18528         int swap=0, wcnt=0, bcnt=0;
18529         if(*p == '[') p++;
18530         if(*p == '<') swap++, p++;
18531         if(*p == '-' ) p++; /* empty holdings */ else {
18532             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18533             /* if we would allow FEN reading to set board size, we would   */
18534             /* have to add holdings and shift the board read so far here   */
18535             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18536                 p++;
18537                 if((int) piece >= (int) BlackPawn ) {
18538                     i = (int)piece - (int)BlackPawn;
18539                     i = PieceToNumber((ChessSquare)i);
18540                     if( i >= gameInfo.holdingsSize ) return FALSE;
18541                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18542                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18543                     bcnt++;
18544                 } else {
18545                     i = (int)piece - (int)WhitePawn;
18546                     i = PieceToNumber((ChessSquare)i);
18547                     if( i >= gameInfo.holdingsSize ) return FALSE;
18548                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18549                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18550                     wcnt++;
18551                 }
18552             }
18553             if(subst) { // substitute back-rank question marks by holdings pieces
18554                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18555                     int k, m, n = bcnt + 1;
18556                     if(board[0][j] == ClearBoard) {
18557                         if(!wcnt) return FALSE;
18558                         n = rand() % wcnt;
18559                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18560                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18561                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18562                             break;
18563                         }
18564                     }
18565                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18566                         if(!bcnt) return FALSE;
18567                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18568                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18569                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18570                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18571                             break;
18572                         }
18573                     }
18574                 }
18575                 subst = 0;
18576             }
18577         }
18578         if(*p == ']') p++;
18579     }
18580
18581     if(subst) return FALSE; // substitution requested, but no holdings
18582
18583     while(*p == ' ') p++;
18584
18585     /* Active color */
18586     c = *p++;
18587     if(appData.colorNickNames) {
18588       if( c == appData.colorNickNames[0] ) c = 'w'; else
18589       if( c == appData.colorNickNames[1] ) c = 'b';
18590     }
18591     switch (c) {
18592       case 'w':
18593         *blackPlaysFirst = FALSE;
18594         break;
18595       case 'b':
18596         *blackPlaysFirst = TRUE;
18597         break;
18598       default:
18599         return FALSE;
18600     }
18601
18602     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18603     /* return the extra info in global variiables             */
18604
18605     while(*p==' ') p++;
18606
18607     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18608         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18609         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18610     }
18611
18612     /* set defaults in case FEN is incomplete */
18613     board[EP_STATUS] = EP_UNKNOWN;
18614     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18615     for(i=0; i<nrCastlingRights; i++ ) {
18616         board[CASTLING][i] =
18617             appData.fischerCastling ? NoRights : initialRights[i];
18618     }   /* assume possible unless obviously impossible */
18619     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18620     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18621     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18622                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18623     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18624     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18625     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18626                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18627     FENrulePlies = 0;
18628
18629     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18630       char *q = p;
18631       int w=0, b=0;
18632       while(isalpha(*p)) {
18633         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18634         if(islower(*p)) b |= 1 << (*p++ - 'a');
18635       }
18636       if(*p == '-') p++;
18637       if(p != q) {
18638         board[TOUCHED_W] = ~w;
18639         board[TOUCHED_B] = ~b;
18640         while(*p == ' ') p++;
18641       }
18642     } else
18643
18644     if(nrCastlingRights) {
18645       int fischer = 0;
18646       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18647       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18648           /* castling indicator present, so default becomes no castlings */
18649           for(i=0; i<nrCastlingRights; i++ ) {
18650                  board[CASTLING][i] = NoRights;
18651           }
18652       }
18653       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18654              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18655              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18656              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18657         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18658
18659         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18660             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18661             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18662         }
18663         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18664             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18665         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18666                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18667         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18668                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18669         switch(c) {
18670           case'K':
18671               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18672               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18673               board[CASTLING][2] = whiteKingFile;
18674               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18675               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18676               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18677               break;
18678           case'Q':
18679               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18680               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18681               board[CASTLING][2] = whiteKingFile;
18682               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18683               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18684               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18685               break;
18686           case'k':
18687               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18688               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18689               board[CASTLING][5] = blackKingFile;
18690               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18691               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18692               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18693               break;
18694           case'q':
18695               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18696               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18697               board[CASTLING][5] = blackKingFile;
18698               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18699               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18700               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18701           case '-':
18702               break;
18703           default: /* FRC castlings */
18704               if(c >= 'a') { /* black rights */
18705                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18706                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18707                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18708                   if(i == BOARD_RGHT) break;
18709                   board[CASTLING][5] = i;
18710                   c -= AAA;
18711                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18712                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18713                   if(c > i)
18714                       board[CASTLING][3] = c;
18715                   else
18716                       board[CASTLING][4] = c;
18717               } else { /* white rights */
18718                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18719                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18720                     if(board[0][i] == WhiteKing) break;
18721                   if(i == BOARD_RGHT) break;
18722                   board[CASTLING][2] = i;
18723                   c -= AAA - 'a' + 'A';
18724                   if(board[0][c] >= WhiteKing) break;
18725                   if(c > i)
18726                       board[CASTLING][0] = c;
18727                   else
18728                       board[CASTLING][1] = c;
18729               }
18730         }
18731       }
18732       for(i=0; i<nrCastlingRights; i++)
18733         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18734       if(gameInfo.variant == VariantSChess)
18735         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18736       if(fischer && shuffle) appData.fischerCastling = TRUE;
18737     if (appData.debugMode) {
18738         fprintf(debugFP, "FEN castling rights:");
18739         for(i=0; i<nrCastlingRights; i++)
18740         fprintf(debugFP, " %d", board[CASTLING][i]);
18741         fprintf(debugFP, "\n");
18742     }
18743
18744       while(*p==' ') p++;
18745     }
18746
18747     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18748
18749     /* read e.p. field in games that know e.p. capture */
18750     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18751        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18752        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18753       if(*p=='-') {
18754         p++; board[EP_STATUS] = EP_NONE;
18755       } else {
18756          char c = *p++ - AAA;
18757
18758          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18759          if(*p >= '0' && *p <='9') p++;
18760          board[EP_STATUS] = c;
18761       }
18762     }
18763
18764
18765     if(sscanf(p, "%d", &i) == 1) {
18766         FENrulePlies = i; /* 50-move ply counter */
18767         /* (The move number is still ignored)    */
18768     }
18769
18770     return TRUE;
18771 }
18772
18773 void
18774 EditPositionPasteFEN (char *fen)
18775 {
18776   if (fen != NULL) {
18777     Board initial_position;
18778
18779     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18780       DisplayError(_("Bad FEN position in clipboard"), 0);
18781       return ;
18782     } else {
18783       int savedBlackPlaysFirst = blackPlaysFirst;
18784       EditPositionEvent();
18785       blackPlaysFirst = savedBlackPlaysFirst;
18786       CopyBoard(boards[0], initial_position);
18787       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18788       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18789       DisplayBothClocks();
18790       DrawPosition(FALSE, boards[currentMove]);
18791     }
18792   }
18793 }
18794
18795 static char cseq[12] = "\\   ";
18796
18797 Boolean
18798 set_cont_sequence (char *new_seq)
18799 {
18800     int len;
18801     Boolean ret;
18802
18803     // handle bad attempts to set the sequence
18804         if (!new_seq)
18805                 return 0; // acceptable error - no debug
18806
18807     len = strlen(new_seq);
18808     ret = (len > 0) && (len < sizeof(cseq));
18809     if (ret)
18810       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18811     else if (appData.debugMode)
18812       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18813     return ret;
18814 }
18815
18816 /*
18817     reformat a source message so words don't cross the width boundary.  internal
18818     newlines are not removed.  returns the wrapped size (no null character unless
18819     included in source message).  If dest is NULL, only calculate the size required
18820     for the dest buffer.  lp argument indicats line position upon entry, and it's
18821     passed back upon exit.
18822 */
18823 int
18824 wrap (char *dest, char *src, int count, int width, int *lp)
18825 {
18826     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18827
18828     cseq_len = strlen(cseq);
18829     old_line = line = *lp;
18830     ansi = len = clen = 0;
18831
18832     for (i=0; i < count; i++)
18833     {
18834         if (src[i] == '\033')
18835             ansi = 1;
18836
18837         // if we hit the width, back up
18838         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18839         {
18840             // store i & len in case the word is too long
18841             old_i = i, old_len = len;
18842
18843             // find the end of the last word
18844             while (i && src[i] != ' ' && src[i] != '\n')
18845             {
18846                 i--;
18847                 len--;
18848             }
18849
18850             // word too long?  restore i & len before splitting it
18851             if ((old_i-i+clen) >= width)
18852             {
18853                 i = old_i;
18854                 len = old_len;
18855             }
18856
18857             // extra space?
18858             if (i && src[i-1] == ' ')
18859                 len--;
18860
18861             if (src[i] != ' ' && src[i] != '\n')
18862             {
18863                 i--;
18864                 if (len)
18865                     len--;
18866             }
18867
18868             // now append the newline and continuation sequence
18869             if (dest)
18870                 dest[len] = '\n';
18871             len++;
18872             if (dest)
18873                 strncpy(dest+len, cseq, cseq_len);
18874             len += cseq_len;
18875             line = cseq_len;
18876             clen = cseq_len;
18877             continue;
18878         }
18879
18880         if (dest)
18881             dest[len] = src[i];
18882         len++;
18883         if (!ansi)
18884             line++;
18885         if (src[i] == '\n')
18886             line = 0;
18887         if (src[i] == 'm')
18888             ansi = 0;
18889     }
18890     if (dest && appData.debugMode)
18891     {
18892         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18893             count, width, line, len, *lp);
18894         show_bytes(debugFP, src, count);
18895         fprintf(debugFP, "\ndest: ");
18896         show_bytes(debugFP, dest, len);
18897         fprintf(debugFP, "\n");
18898     }
18899     *lp = dest ? line : old_line;
18900
18901     return len;
18902 }
18903
18904 // [HGM] vari: routines for shelving variations
18905 Boolean modeRestore = FALSE;
18906
18907 void
18908 PushInner (int firstMove, int lastMove)
18909 {
18910         int i, j, nrMoves = lastMove - firstMove;
18911
18912         // push current tail of game on stack
18913         savedResult[storedGames] = gameInfo.result;
18914         savedDetails[storedGames] = gameInfo.resultDetails;
18915         gameInfo.resultDetails = NULL;
18916         savedFirst[storedGames] = firstMove;
18917         savedLast [storedGames] = lastMove;
18918         savedFramePtr[storedGames] = framePtr;
18919         framePtr -= nrMoves; // reserve space for the boards
18920         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18921             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18922             for(j=0; j<MOVE_LEN; j++)
18923                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18924             for(j=0; j<2*MOVE_LEN; j++)
18925                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18926             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18927             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18928             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18929             pvInfoList[firstMove+i-1].depth = 0;
18930             commentList[framePtr+i] = commentList[firstMove+i];
18931             commentList[firstMove+i] = NULL;
18932         }
18933
18934         storedGames++;
18935         forwardMostMove = firstMove; // truncate game so we can start variation
18936 }
18937
18938 void
18939 PushTail (int firstMove, int lastMove)
18940 {
18941         if(appData.icsActive) { // only in local mode
18942                 forwardMostMove = currentMove; // mimic old ICS behavior
18943                 return;
18944         }
18945         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18946
18947         PushInner(firstMove, lastMove);
18948         if(storedGames == 1) GreyRevert(FALSE);
18949         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18950 }
18951
18952 void
18953 PopInner (Boolean annotate)
18954 {
18955         int i, j, nrMoves;
18956         char buf[8000], moveBuf[20];
18957
18958         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18959         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18960         nrMoves = savedLast[storedGames] - currentMove;
18961         if(annotate) {
18962                 int cnt = 10;
18963                 if(!WhiteOnMove(currentMove))
18964                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18965                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18966                 for(i=currentMove; i<forwardMostMove; i++) {
18967                         if(WhiteOnMove(i))
18968                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18969                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18970                         strcat(buf, moveBuf);
18971                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18972                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18973                 }
18974                 strcat(buf, ")");
18975         }
18976         for(i=1; i<=nrMoves; i++) { // copy last variation back
18977             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18978             for(j=0; j<MOVE_LEN; j++)
18979                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18980             for(j=0; j<2*MOVE_LEN; j++)
18981                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18982             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18983             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18984             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18985             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18986             commentList[currentMove+i] = commentList[framePtr+i];
18987             commentList[framePtr+i] = NULL;
18988         }
18989         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18990         framePtr = savedFramePtr[storedGames];
18991         gameInfo.result = savedResult[storedGames];
18992         if(gameInfo.resultDetails != NULL) {
18993             free(gameInfo.resultDetails);
18994       }
18995         gameInfo.resultDetails = savedDetails[storedGames];
18996         forwardMostMove = currentMove + nrMoves;
18997 }
18998
18999 Boolean
19000 PopTail (Boolean annotate)
19001 {
19002         if(appData.icsActive) return FALSE; // only in local mode
19003         if(!storedGames) return FALSE; // sanity
19004         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19005
19006         PopInner(annotate);
19007         if(currentMove < forwardMostMove) ForwardEvent(); else
19008         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19009
19010         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19011         return TRUE;
19012 }
19013
19014 void
19015 CleanupTail ()
19016 {       // remove all shelved variations
19017         int i;
19018         for(i=0; i<storedGames; i++) {
19019             if(savedDetails[i])
19020                 free(savedDetails[i]);
19021             savedDetails[i] = NULL;
19022         }
19023         for(i=framePtr; i<MAX_MOVES; i++) {
19024                 if(commentList[i]) free(commentList[i]);
19025                 commentList[i] = NULL;
19026         }
19027         framePtr = MAX_MOVES-1;
19028         storedGames = 0;
19029 }
19030
19031 void
19032 LoadVariation (int index, char *text)
19033 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19034         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19035         int level = 0, move;
19036
19037         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19038         // first find outermost bracketing variation
19039         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19040             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19041                 if(*p == '{') wait = '}'; else
19042                 if(*p == '[') wait = ']'; else
19043                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19044                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19045             }
19046             if(*p == wait) wait = NULLCHAR; // closing ]} found
19047             p++;
19048         }
19049         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19050         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19051         end[1] = NULLCHAR; // clip off comment beyond variation
19052         ToNrEvent(currentMove-1);
19053         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19054         // kludge: use ParsePV() to append variation to game
19055         move = currentMove;
19056         ParsePV(start, TRUE, TRUE);
19057         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19058         ClearPremoveHighlights();
19059         CommentPopDown();
19060         ToNrEvent(currentMove+1);
19061 }
19062
19063 int transparency[2];
19064
19065 void
19066 LoadTheme ()
19067 {
19068     char *p, *q, buf[MSG_SIZ];
19069     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19070         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19071         ParseArgsFromString(buf);
19072         ActivateTheme(TRUE); // also redo colors
19073         return;
19074     }
19075     p = nickName;
19076     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19077     {
19078         int len;
19079         q = appData.themeNames;
19080         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19081       if(appData.useBitmaps) {
19082         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19083                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19084                 appData.liteBackTextureMode,
19085                 appData.darkBackTextureMode );
19086       } else {
19087         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false");
19088       }
19089       if(!appData.useBitmaps || transparency[0]) {
19090         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19091       }
19092       if(!appData.useBitmaps || transparency[1]) {
19093         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19094       }
19095       if(appData.useBorder) {
19096         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19097                 appData.border);
19098       } else {
19099         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19100       }
19101       if(appData.useFont) {
19102         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19103                 appData.renderPiecesWithFont,
19104                 appData.fontToPieceTable,
19105                 Col2Text(9),    // appData.fontBackColorWhite
19106                 Col2Text(10) ); // appData.fontForeColorBlack
19107       } else {
19108         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false");
19109         if(appData.pieceDirectory[0]) {
19110           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -pid \"%s\"", appData.pieceDirectory);
19111           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19112             snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19113         }
19114         if(!appData.pieceDirectory[0] || !appData.trueColors)
19115           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19116                 Col2Text(0),   // whitePieceColor
19117                 Col2Text(1) ); // blackPieceColor
19118       }
19119       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19120                 Col2Text(4),   // highlightSquareColor
19121                 Col2Text(5) ); // premoveHighlightColor
19122         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19123         if(insert != q) insert[-1] = NULLCHAR;
19124         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19125         if(q)   free(q);
19126     }
19127     ActivateTheme(FALSE);
19128 }