79cc86cd2d1ec7a3e799392f01f51d8d7df47552
[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]) { // 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\n", AAA + killX, ONE + killY);
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     piece = boards[currentMove][fromY][fromX];
6706     if(gameInfo.variant == VariantChu) {
6707         promotionZoneSize = BOARD_HEIGHT/3;
6708         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6709     } else if(gameInfo.variant == VariantShogi) {
6710         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6711         highestPromotingPiece = (int)WhiteAlfil;
6712     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6713         promotionZoneSize = 3;
6714     }
6715
6716     // Treat Lance as Pawn when it is not representing Amazon or Lance
6717     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6718         if(piece == WhiteLance) piece = WhitePawn; else
6719         if(piece == BlackLance) piece = BlackPawn;
6720     }
6721
6722     // next weed out all moves that do not touch the promotion zone at all
6723     if((int)piece >= BlackPawn) {
6724         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6725              return FALSE;
6726         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6727         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6728     } else {
6729         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6730            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6731         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6732              return FALSE;
6733     }
6734
6735     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6736
6737     // weed out mandatory Shogi promotions
6738     if(gameInfo.variant == VariantShogi) {
6739         if(piece >= BlackPawn) {
6740             if(toY == 0 && piece == BlackPawn ||
6741                toY == 0 && piece == BlackQueen ||
6742                toY <= 1 && piece == BlackKnight) {
6743                 *promoChoice = '+';
6744                 return FALSE;
6745             }
6746         } else {
6747             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6748                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6749                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6750                 *promoChoice = '+';
6751                 return FALSE;
6752             }
6753         }
6754     }
6755
6756     // weed out obviously illegal Pawn moves
6757     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6758         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6759         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6760         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6761         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6762         // note we are not allowed to test for valid (non-)capture, due to premove
6763     }
6764
6765     // we either have a choice what to promote to, or (in Shogi) whether to promote
6766     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6767        gameInfo.variant == VariantMakruk) {
6768         ChessSquare p=BlackFerz;  // no choice
6769         while(p < EmptySquare) {  //but make sure we use piece that exists
6770             *promoChoice = PieceToChar(p++);
6771             if(*promoChoice != '.') break;
6772         }
6773         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6774     }
6775     // no sense asking what we must promote to if it is going to explode...
6776     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6777         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6778         return FALSE;
6779     }
6780     // give caller the default choice even if we will not make it
6781     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6782     partner = piece; // pieces can promote if the pieceToCharTable says so
6783     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6784     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6785     if(        sweepSelect && gameInfo.variant != VariantGreat
6786                            && gameInfo.variant != VariantGrand
6787                            && gameInfo.variant != VariantSuper) return FALSE;
6788     if(autoQueen) return FALSE; // predetermined
6789
6790     // suppress promotion popup on illegal moves that are not premoves
6791     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6792               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6793     if(appData.testLegality && !premove) {
6794         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6795                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6796         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6797         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6798             return FALSE;
6799     }
6800
6801     return TRUE;
6802 }
6803
6804 int
6805 InPalace (int row, int column)
6806 {   /* [HGM] for Xiangqi */
6807     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6808          column < (BOARD_WIDTH + 4)/2 &&
6809          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6810     return FALSE;
6811 }
6812
6813 int
6814 PieceForSquare (int x, int y)
6815 {
6816   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6817      return -1;
6818   else
6819      return boards[currentMove][y][x];
6820 }
6821
6822 int
6823 OKToStartUserMove (int x, int y)
6824 {
6825     ChessSquare from_piece;
6826     int white_piece;
6827
6828     if (matchMode) return FALSE;
6829     if (gameMode == EditPosition) return TRUE;
6830
6831     if (x >= 0 && y >= 0)
6832       from_piece = boards[currentMove][y][x];
6833     else
6834       from_piece = EmptySquare;
6835
6836     if (from_piece == EmptySquare) return FALSE;
6837
6838     white_piece = (int)from_piece >= (int)WhitePawn &&
6839       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6840
6841     switch (gameMode) {
6842       case AnalyzeFile:
6843       case TwoMachinesPlay:
6844       case EndOfGame:
6845         return FALSE;
6846
6847       case IcsObserving:
6848       case IcsIdle:
6849         return FALSE;
6850
6851       case MachinePlaysWhite:
6852       case IcsPlayingBlack:
6853         if (appData.zippyPlay) return FALSE;
6854         if (white_piece) {
6855             DisplayMoveError(_("You are playing Black"));
6856             return FALSE;
6857         }
6858         break;
6859
6860       case MachinePlaysBlack:
6861       case IcsPlayingWhite:
6862         if (appData.zippyPlay) return FALSE;
6863         if (!white_piece) {
6864             DisplayMoveError(_("You are playing White"));
6865             return FALSE;
6866         }
6867         break;
6868
6869       case PlayFromGameFile:
6870             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6871       case EditGame:
6872       case AnalyzeMode:
6873         if (!white_piece && WhiteOnMove(currentMove)) {
6874             DisplayMoveError(_("It is White's turn"));
6875             return FALSE;
6876         }
6877         if (white_piece && !WhiteOnMove(currentMove)) {
6878             DisplayMoveError(_("It is Black's turn"));
6879             return FALSE;
6880         }
6881         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6882             /* Editing correspondence game history */
6883             /* Could disallow this or prompt for confirmation */
6884             cmailOldMove = -1;
6885         }
6886         break;
6887
6888       case BeginningOfGame:
6889         if (appData.icsActive) return FALSE;
6890         if (!appData.noChessProgram) {
6891             if (!white_piece) {
6892                 DisplayMoveError(_("You are playing White"));
6893                 return FALSE;
6894             }
6895         }
6896         break;
6897
6898       case Training:
6899         if (!white_piece && WhiteOnMove(currentMove)) {
6900             DisplayMoveError(_("It is White's turn"));
6901             return FALSE;
6902         }
6903         if (white_piece && !WhiteOnMove(currentMove)) {
6904             DisplayMoveError(_("It is Black's turn"));
6905             return FALSE;
6906         }
6907         break;
6908
6909       default:
6910       case IcsExamining:
6911         break;
6912     }
6913     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6914         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6915         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6916         && gameMode != AnalyzeFile && gameMode != Training) {
6917         DisplayMoveError(_("Displayed position is not current"));
6918         return FALSE;
6919     }
6920     return TRUE;
6921 }
6922
6923 Boolean
6924 OnlyMove (int *x, int *y, Boolean captures)
6925 {
6926     DisambiguateClosure cl;
6927     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6928     switch(gameMode) {
6929       case MachinePlaysBlack:
6930       case IcsPlayingWhite:
6931       case BeginningOfGame:
6932         if(!WhiteOnMove(currentMove)) return FALSE;
6933         break;
6934       case MachinePlaysWhite:
6935       case IcsPlayingBlack:
6936         if(WhiteOnMove(currentMove)) return FALSE;
6937         break;
6938       case EditGame:
6939         break;
6940       default:
6941         return FALSE;
6942     }
6943     cl.pieceIn = EmptySquare;
6944     cl.rfIn = *y;
6945     cl.ffIn = *x;
6946     cl.rtIn = -1;
6947     cl.ftIn = -1;
6948     cl.promoCharIn = NULLCHAR;
6949     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6950     if( cl.kind == NormalMove ||
6951         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6952         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6953         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6954       fromX = cl.ff;
6955       fromY = cl.rf;
6956       *x = cl.ft;
6957       *y = cl.rt;
6958       return TRUE;
6959     }
6960     if(cl.kind != ImpossibleMove) return FALSE;
6961     cl.pieceIn = EmptySquare;
6962     cl.rfIn = -1;
6963     cl.ffIn = -1;
6964     cl.rtIn = *y;
6965     cl.ftIn = *x;
6966     cl.promoCharIn = NULLCHAR;
6967     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6968     if( cl.kind == NormalMove ||
6969         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6970         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6971         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6972       fromX = cl.ff;
6973       fromY = cl.rf;
6974       *x = cl.ft;
6975       *y = cl.rt;
6976       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6977       return TRUE;
6978     }
6979     return FALSE;
6980 }
6981
6982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6984 int lastLoadGameUseList = FALSE;
6985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6986 ChessMove lastLoadGameStart = EndOfFile;
6987 int doubleClick;
6988 Boolean addToBookFlag;
6989 static Board rightsBoard, nullBoard;
6990
6991 void
6992 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6993 {
6994     ChessMove moveType;
6995     ChessSquare pup;
6996     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6997
6998     /* Check if the user is playing in turn.  This is complicated because we
6999        let the user "pick up" a piece before it is his turn.  So the piece he
7000        tried to pick up may have been captured by the time he puts it down!
7001        Therefore we use the color the user is supposed to be playing in this
7002        test, not the color of the piece that is currently on the starting
7003        square---except in EditGame mode, where the user is playing both
7004        sides; fortunately there the capture race can't happen.  (It can
7005        now happen in IcsExamining mode, but that's just too bad.  The user
7006        will get a somewhat confusing message in that case.)
7007        */
7008
7009     switch (gameMode) {
7010       case AnalyzeFile:
7011       case TwoMachinesPlay:
7012       case EndOfGame:
7013       case IcsObserving:
7014       case IcsIdle:
7015         /* We switched into a game mode where moves are not accepted,
7016            perhaps while the mouse button was down. */
7017         return;
7018
7019       case MachinePlaysWhite:
7020         /* User is moving for Black */
7021         if (WhiteOnMove(currentMove)) {
7022             DisplayMoveError(_("It is White's turn"));
7023             return;
7024         }
7025         break;
7026
7027       case MachinePlaysBlack:
7028         /* User is moving for White */
7029         if (!WhiteOnMove(currentMove)) {
7030             DisplayMoveError(_("It is Black's turn"));
7031             return;
7032         }
7033         break;
7034
7035       case PlayFromGameFile:
7036             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7037       case EditGame:
7038       case IcsExamining:
7039       case BeginningOfGame:
7040       case AnalyzeMode:
7041       case Training:
7042         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7043         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7044             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7045             /* User is moving for Black */
7046             if (WhiteOnMove(currentMove)) {
7047                 DisplayMoveError(_("It is White's turn"));
7048                 return;
7049             }
7050         } else {
7051             /* User is moving for White */
7052             if (!WhiteOnMove(currentMove)) {
7053                 DisplayMoveError(_("It is Black's turn"));
7054                 return;
7055             }
7056         }
7057         break;
7058
7059       case IcsPlayingBlack:
7060         /* User is moving for Black */
7061         if (WhiteOnMove(currentMove)) {
7062             if (!appData.premove) {
7063                 DisplayMoveError(_("It is White's turn"));
7064             } else if (toX >= 0 && toY >= 0) {
7065                 premoveToX = toX;
7066                 premoveToY = toY;
7067                 premoveFromX = fromX;
7068                 premoveFromY = fromY;
7069                 premovePromoChar = promoChar;
7070                 gotPremove = 1;
7071                 if (appData.debugMode)
7072                     fprintf(debugFP, "Got premove: fromX %d,"
7073                             "fromY %d, toX %d, toY %d\n",
7074                             fromX, fromY, toX, toY);
7075             }
7076             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7077             return;
7078         }
7079         break;
7080
7081       case IcsPlayingWhite:
7082         /* User is moving for White */
7083         if (!WhiteOnMove(currentMove)) {
7084             if (!appData.premove) {
7085                 DisplayMoveError(_("It is Black's turn"));
7086             } else if (toX >= 0 && toY >= 0) {
7087                 premoveToX = toX;
7088                 premoveToY = toY;
7089                 premoveFromX = fromX;
7090                 premoveFromY = fromY;
7091                 premovePromoChar = promoChar;
7092                 gotPremove = 1;
7093                 if (appData.debugMode)
7094                     fprintf(debugFP, "Got premove: fromX %d,"
7095                             "fromY %d, toX %d, toY %d\n",
7096                             fromX, fromY, toX, toY);
7097             }
7098             DrawPosition(TRUE, boards[currentMove]);
7099             return;
7100         }
7101         break;
7102
7103       default:
7104         break;
7105
7106       case EditPosition:
7107         /* EditPosition, empty square, or different color piece;
7108            click-click move is possible */
7109         if (toX == -2 || toY == -2) {
7110             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7111             DrawPosition(FALSE, boards[currentMove]);
7112             return;
7113         } else if (toX >= 0 && toY >= 0) {
7114             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7115                 ChessSquare p = boards[0][rf][ff];
7116                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7117                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7118                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7119                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7120                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7121                     gatingPiece = p;
7122                 }
7123             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7124             boards[0][toY][toX] = boards[0][fromY][fromX];
7125             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7126                 if(boards[0][fromY][0] != EmptySquare) {
7127                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7128                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7129                 }
7130             } else
7131             if(fromX == BOARD_RGHT+1) {
7132                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7133                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7134                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7135                 }
7136             } else
7137             boards[0][fromY][fromX] = gatingPiece;
7138             ClearHighlights();
7139             DrawPosition(FALSE, boards[currentMove]);
7140             return;
7141         }
7142         return;
7143     }
7144
7145     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7146     pup = boards[currentMove][toY][toX];
7147
7148     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7149     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7150          if( pup != EmptySquare ) return;
7151          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7152            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7153                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7154            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7155            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7156            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7157            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7158          fromY = DROP_RANK;
7159     }
7160
7161     /* [HGM] always test for legality, to get promotion info */
7162     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7163                                          fromY, fromX, toY, toX, promoChar);
7164
7165     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7166
7167     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7168
7169     /* [HGM] but possibly ignore an IllegalMove result */
7170     if (appData.testLegality) {
7171         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7172             DisplayMoveError(_("Illegal move"));
7173             return;
7174         }
7175     }
7176
7177     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7178         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7179              ClearPremoveHighlights(); // was included
7180         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7181         DrawPosition(FALSE, NULL);
7182         return;
7183     }
7184
7185     if(addToBookFlag) { // adding moves to book
7186         char buf[MSG_SIZ], move[MSG_SIZ];
7187         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7188         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7189                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7190         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7191         AddBookMove(buf);
7192         addToBookFlag = FALSE;
7193         ClearHighlights();
7194         return;
7195     }
7196
7197     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7198 }
7199
7200 /* Common tail of UserMoveEvent and DropMenuEvent */
7201 int
7202 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7203 {
7204     char *bookHit = 0;
7205
7206     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7207         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7208         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7209         if(WhiteOnMove(currentMove)) {
7210             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7211         } else {
7212             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7213         }
7214     }
7215
7216     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7217        move type in caller when we know the move is a legal promotion */
7218     if(moveType == NormalMove && promoChar)
7219         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7220
7221     /* [HGM] <popupFix> The following if has been moved here from
7222        UserMoveEvent(). Because it seemed to belong here (why not allow
7223        piece drops in training games?), and because it can only be
7224        performed after it is known to what we promote. */
7225     if (gameMode == Training) {
7226       /* compare the move played on the board to the next move in the
7227        * game. If they match, display the move and the opponent's response.
7228        * If they don't match, display an error message.
7229        */
7230       int saveAnimate;
7231       Board testBoard;
7232       CopyBoard(testBoard, boards[currentMove]);
7233       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7234
7235       if (CompareBoards(testBoard, boards[currentMove+1])) {
7236         ForwardInner(currentMove+1);
7237
7238         /* Autoplay the opponent's response.
7239          * if appData.animate was TRUE when Training mode was entered,
7240          * the response will be animated.
7241          */
7242         saveAnimate = appData.animate;
7243         appData.animate = animateTraining;
7244         ForwardInner(currentMove+1);
7245         appData.animate = saveAnimate;
7246
7247         /* check for the end of the game */
7248         if (currentMove >= forwardMostMove) {
7249           gameMode = PlayFromGameFile;
7250           ModeHighlight();
7251           SetTrainingModeOff();
7252           DisplayInformation(_("End of game"));
7253         }
7254       } else {
7255         DisplayError(_("Incorrect move"), 0);
7256       }
7257       return 1;
7258     }
7259
7260   /* Ok, now we know that the move is good, so we can kill
7261      the previous line in Analysis Mode */
7262   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7263                                 && currentMove < forwardMostMove) {
7264     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7265     else forwardMostMove = currentMove;
7266   }
7267
7268   ClearMap();
7269
7270   /* If we need the chess program but it's dead, restart it */
7271   ResurrectChessProgram();
7272
7273   /* A user move restarts a paused game*/
7274   if (pausing)
7275     PauseEvent();
7276
7277   thinkOutput[0] = NULLCHAR;
7278
7279   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7280
7281   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7282     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7283     return 1;
7284   }
7285
7286   if (gameMode == BeginningOfGame) {
7287     if (appData.noChessProgram) {
7288       gameMode = EditGame;
7289       SetGameInfo();
7290     } else {
7291       char buf[MSG_SIZ];
7292       gameMode = MachinePlaysBlack;
7293       StartClocks();
7294       SetGameInfo();
7295       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7296       DisplayTitle(buf);
7297       if (first.sendName) {
7298         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7299         SendToProgram(buf, &first);
7300       }
7301       StartClocks();
7302     }
7303     ModeHighlight();
7304   }
7305
7306   /* Relay move to ICS or chess engine */
7307   if (appData.icsActive) {
7308     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7309         gameMode == IcsExamining) {
7310       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7311         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7312         SendToICS("draw ");
7313         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7314       }
7315       // also send plain move, in case ICS does not understand atomic claims
7316       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7317       ics_user_moved = 1;
7318     }
7319   } else {
7320     if (first.sendTime && (gameMode == BeginningOfGame ||
7321                            gameMode == MachinePlaysWhite ||
7322                            gameMode == MachinePlaysBlack)) {
7323       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7324     }
7325     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7326          // [HGM] book: if program might be playing, let it use book
7327         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7328         first.maybeThinking = TRUE;
7329     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7330         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7331         SendBoard(&first, currentMove+1);
7332         if(second.analyzing) {
7333             if(!second.useSetboard) SendToProgram("undo\n", &second);
7334             SendBoard(&second, currentMove+1);
7335         }
7336     } else {
7337         SendMoveToProgram(forwardMostMove-1, &first);
7338         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7339     }
7340     if (currentMove == cmailOldMove + 1) {
7341       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7342     }
7343   }
7344
7345   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7346
7347   switch (gameMode) {
7348   case EditGame:
7349     if(appData.testLegality)
7350     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7351     case MT_NONE:
7352     case MT_CHECK:
7353       break;
7354     case MT_CHECKMATE:
7355     case MT_STAINMATE:
7356       if (WhiteOnMove(currentMove)) {
7357         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7358       } else {
7359         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7360       }
7361       break;
7362     case MT_STALEMATE:
7363       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7364       break;
7365     }
7366     break;
7367
7368   case MachinePlaysBlack:
7369   case MachinePlaysWhite:
7370     /* disable certain menu options while machine is thinking */
7371     SetMachineThinkingEnables();
7372     break;
7373
7374   default:
7375     break;
7376   }
7377
7378   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7379   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7380
7381   if(bookHit) { // [HGM] book: simulate book reply
7382         static char bookMove[MSG_SIZ]; // a bit generous?
7383
7384         programStats.nodes = programStats.depth = programStats.time =
7385         programStats.score = programStats.got_only_move = 0;
7386         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7387
7388         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7389         strcat(bookMove, bookHit);
7390         HandleMachineMove(bookMove, &first);
7391   }
7392   return 1;
7393 }
7394
7395 void
7396 MarkByFEN(char *fen)
7397 {
7398         int r, f;
7399         if(!appData.markers || !appData.highlightDragging) return;
7400         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7401         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7402         while(*fen) {
7403             int s = 0;
7404             marker[r][f] = 0;
7405             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7406             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7407             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7408             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7409             if(*fen == 'T') marker[r][f++] = 0; else
7410             if(*fen == 'Y') marker[r][f++] = 1; else
7411             if(*fen == 'G') marker[r][f++] = 3; else
7412             if(*fen == 'B') marker[r][f++] = 4; else
7413             if(*fen == 'C') marker[r][f++] = 5; else
7414             if(*fen == 'M') marker[r][f++] = 6; else
7415             if(*fen == 'W') marker[r][f++] = 7; else
7416             if(*fen == 'D') marker[r][f++] = 8; else
7417             if(*fen == 'R') marker[r][f++] = 2; else {
7418                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7419               f += s; fen -= s>0;
7420             }
7421             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7422             if(r < 0) break;
7423             fen++;
7424         }
7425         DrawPosition(TRUE, NULL);
7426 }
7427
7428 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7429
7430 void
7431 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7432 {
7433     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7434     Markers *m = (Markers *) closure;
7435     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7436                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7437         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7438                          || kind == WhiteCapturesEnPassant
7439                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7440     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7441 }
7442
7443 static int hoverSavedValid;
7444
7445 void
7446 MarkTargetSquares (int clear)
7447 {
7448   int x, y, sum=0;
7449   if(clear) { // no reason to ever suppress clearing
7450     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7451     hoverSavedValid = 0;
7452     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7453   } else {
7454     int capt = 0;
7455     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7456        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7457     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7458     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7459       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7460       if(capt)
7461       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7462     }
7463   }
7464   DrawPosition(FALSE, NULL);
7465 }
7466
7467 int
7468 Explode (Board board, int fromX, int fromY, int toX, int toY)
7469 {
7470     if(gameInfo.variant == VariantAtomic &&
7471        (board[toY][toX] != EmptySquare ||                     // capture?
7472         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7473                          board[fromY][fromX] == BlackPawn   )
7474       )) {
7475         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7476         return TRUE;
7477     }
7478     return FALSE;
7479 }
7480
7481 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7482
7483 int
7484 CanPromote (ChessSquare piece, int y)
7485 {
7486         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7487         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7488         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7489         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7490            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7491           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7492            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7493         return (piece == BlackPawn && y <= zone ||
7494                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7495                 piece == BlackLance && y <= zone ||
7496                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7497 }
7498
7499 void
7500 HoverEvent (int xPix, int yPix, int x, int y)
7501 {
7502         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7503         int r, f;
7504         if(!first.highlight) return;
7505         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7506         if(x == oldX && y == oldY) return; // only do something if we enter new square
7507         oldFromX = fromX; oldFromY = fromY;
7508         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7509           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7510             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7511           hoverSavedValid = 1;
7512         } else if(oldX != x || oldY != y) {
7513           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7514           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7515           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7516             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7517           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7518             char buf[MSG_SIZ];
7519             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7520             SendToProgram(buf, &first);
7521           }
7522           oldX = x; oldY = y;
7523 //        SetHighlights(fromX, fromY, x, y);
7524         }
7525 }
7526
7527 void ReportClick(char *action, int x, int y)
7528 {
7529         char buf[MSG_SIZ]; // Inform engine of what user does
7530         int r, f;
7531         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7532           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7533             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7534         if(!first.highlight || gameMode == EditPosition) return;
7535         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7536         SendToProgram(buf, &first);
7537 }
7538
7539 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7540
7541 void
7542 LeftClick (ClickType clickType, int xPix, int yPix)
7543 {
7544     int x, y;
7545     Boolean saveAnimate;
7546     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7547     char promoChoice = NULLCHAR;
7548     ChessSquare piece;
7549     static TimeMark lastClickTime, prevClickTime;
7550
7551     if(flashing) return;
7552
7553     x = EventToSquare(xPix, BOARD_WIDTH);
7554     y = EventToSquare(yPix, BOARD_HEIGHT);
7555     if (!flipView && y >= 0) {
7556         y = BOARD_HEIGHT - 1 - y;
7557     }
7558     if (flipView && x >= 0) {
7559         x = BOARD_WIDTH - 1 - x;
7560     }
7561
7562     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7563         static int dummy;
7564         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7565         right = TRUE;
7566         return;
7567     }
7568
7569     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7570
7571     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7572
7573     if (clickType == Press) ErrorPopDown();
7574     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7575
7576     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7577         defaultPromoChoice = promoSweep;
7578         promoSweep = EmptySquare;   // terminate sweep
7579         promoDefaultAltered = TRUE;
7580         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7581     }
7582
7583     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7584         if(clickType == Release) return; // ignore upclick of click-click destination
7585         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7586         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7587         if(gameInfo.holdingsWidth &&
7588                 (WhiteOnMove(currentMove)
7589                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7590                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7591             // click in right holdings, for determining promotion piece
7592             ChessSquare p = boards[currentMove][y][x];
7593             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7594             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7595             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7596                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7597                 fromX = fromY = -1;
7598                 return;
7599             }
7600         }
7601         DrawPosition(FALSE, boards[currentMove]);
7602         return;
7603     }
7604
7605     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7606     if(clickType == Press
7607             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7608               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7609               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7610         return;
7611
7612     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7613         // could be static click on premove from-square: abort premove
7614         gotPremove = 0;
7615         ClearPremoveHighlights();
7616     }
7617
7618     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7619         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7620
7621     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7622         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7623                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7624         defaultPromoChoice = DefaultPromoChoice(side);
7625     }
7626
7627     autoQueen = appData.alwaysPromoteToQueen;
7628
7629     if (fromX == -1) {
7630       int originalY = y;
7631       gatingPiece = EmptySquare;
7632       if (clickType != Press) {
7633         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7634             DragPieceEnd(xPix, yPix); dragging = 0;
7635             DrawPosition(FALSE, NULL);
7636         }
7637         return;
7638       }
7639       doubleClick = FALSE;
7640       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7641         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7642       }
7643       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7644       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7645          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7646          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7647             /* First square */
7648             if (OKToStartUserMove(fromX, fromY)) {
7649                 second = 0;
7650                 ReportClick("lift", x, y);
7651                 MarkTargetSquares(0);
7652                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7653                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7654                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7655                     promoSweep = defaultPromoChoice;
7656                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7657                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7658                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7659                 }
7660                 if (appData.highlightDragging) {
7661                     SetHighlights(fromX, fromY, -1, -1);
7662                 } else {
7663                     ClearHighlights();
7664                 }
7665             } else fromX = fromY = -1;
7666             return;
7667         }
7668     }
7669
7670     /* fromX != -1 */
7671     if (clickType == Press && gameMode != EditPosition) {
7672         ChessSquare fromP;
7673         ChessSquare toP;
7674         int frc;
7675
7676         // ignore off-board to clicks
7677         if(y < 0 || x < 0) return;
7678
7679         /* Check if clicking again on the same color piece */
7680         fromP = boards[currentMove][fromY][fromX];
7681         toP = boards[currentMove][y][x];
7682         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7683         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7684             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7685            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7686              WhitePawn <= toP && toP <= WhiteKing &&
7687              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7688              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7689             (BlackPawn <= fromP && fromP <= BlackKing &&
7690              BlackPawn <= toP && toP <= BlackKing &&
7691              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7692              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7693             /* Clicked again on same color piece -- changed his mind */
7694             second = (x == fromX && y == fromY);
7695             killX = killY = kill2X = kill2Y = -1;
7696             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7697                 second = FALSE; // first double-click rather than scond click
7698                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7699             }
7700             promoDefaultAltered = FALSE;
7701            if(!second) MarkTargetSquares(1);
7702            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7703             if (appData.highlightDragging) {
7704                 SetHighlights(x, y, -1, -1);
7705             } else {
7706                 ClearHighlights();
7707             }
7708             if (OKToStartUserMove(x, y)) {
7709                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7710                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7711                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7712                  gatingPiece = boards[currentMove][fromY][fromX];
7713                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7714                 fromX = x;
7715                 fromY = y; dragging = 1;
7716                 if(!second) ReportClick("lift", x, y);
7717                 MarkTargetSquares(0);
7718                 DragPieceBegin(xPix, yPix, FALSE);
7719                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7720                     promoSweep = defaultPromoChoice;
7721                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7722                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7723                 }
7724             }
7725            }
7726            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7727            second = FALSE;
7728         }
7729         // ignore clicks on holdings
7730         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7731     }
7732
7733     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7734         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7735         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7736         return;
7737     }
7738
7739     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7740         DragPieceEnd(xPix, yPix); dragging = 0;
7741         if(clearFlag) {
7742             // a deferred attempt to click-click move an empty square on top of a piece
7743             boards[currentMove][y][x] = EmptySquare;
7744             ClearHighlights();
7745             DrawPosition(FALSE, boards[currentMove]);
7746             fromX = fromY = -1; clearFlag = 0;
7747             return;
7748         }
7749         if (appData.animateDragging) {
7750             /* Undo animation damage if any */
7751             DrawPosition(FALSE, NULL);
7752         }
7753         if (second) {
7754             /* Second up/down in same square; just abort move */
7755             second = 0;
7756             fromX = fromY = -1;
7757             gatingPiece = EmptySquare;
7758             ClearHighlights();
7759             gotPremove = 0;
7760             ClearPremoveHighlights();
7761             MarkTargetSquares(-1);
7762             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7763         } else {
7764             /* First upclick in same square; start click-click mode */
7765             SetHighlights(x, y, -1, -1);
7766         }
7767         return;
7768     }
7769
7770     clearFlag = 0;
7771
7772     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7773        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7774         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7775         DisplayMessage(_("only marked squares are legal"),"");
7776         DrawPosition(TRUE, NULL);
7777         return; // ignore to-click
7778     }
7779
7780     /* we now have a different from- and (possibly off-board) to-square */
7781     /* Completed move */
7782     if(!sweepSelecting) {
7783         toX = x;
7784         toY = y;
7785     }
7786
7787     piece = boards[currentMove][fromY][fromX];
7788
7789     saveAnimate = appData.animate;
7790     if (clickType == Press) {
7791         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7792         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7793             // must be Edit Position mode with empty-square selected
7794             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7795             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7796             return;
7797         }
7798         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7799             return;
7800         }
7801         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7802             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7803         } else
7804         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7805         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7806           if(appData.sweepSelect) {
7807             promoSweep = defaultPromoChoice;
7808             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7809             selectFlag = 0; lastX = xPix; lastY = yPix;
7810             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7811             saveFlash = appData.flashCount; appData.flashCount = 0;
7812             Sweep(0); // Pawn that is going to promote: preview promotion piece
7813             sweepSelecting = 1;
7814             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7815             MarkTargetSquares(1);
7816           }
7817           return; // promo popup appears on up-click
7818         }
7819         /* Finish clickclick move */
7820         if (appData.animate || appData.highlightLastMove) {
7821             SetHighlights(fromX, fromY, toX, toY);
7822         } else {
7823             ClearHighlights();
7824         }
7825         MarkTargetSquares(1);
7826     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7827         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7828         *promoRestrict = 0; appData.flashCount = saveFlash;
7829         if (appData.animate || appData.highlightLastMove) {
7830             SetHighlights(fromX, fromY, toX, toY);
7831         } else {
7832             ClearHighlights();
7833         }
7834         MarkTargetSquares(1);
7835     } else {
7836 #if 0
7837 // [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
7838         /* Finish drag move */
7839         if (appData.highlightLastMove) {
7840             SetHighlights(fromX, fromY, toX, toY);
7841         } else {
7842             ClearHighlights();
7843         }
7844 #endif
7845         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7846           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7847         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7848         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7849           dragging *= 2;            // flag button-less dragging if we are dragging
7850           MarkTargetSquares(1);
7851           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7852           else {
7853             kill2X = killX; kill2Y = killY;
7854             killX = x; killY = y;     // remember this square as intermediate
7855             ReportClick("put", x, y); // and inform engine
7856             ReportClick("lift", x, y);
7857             MarkTargetSquares(0);
7858             return;
7859           }
7860         }
7861         DragPieceEnd(xPix, yPix); dragging = 0;
7862         /* Don't animate move and drag both */
7863         appData.animate = FALSE;
7864         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7865     }
7866
7867     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7868     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7869         ChessSquare piece = boards[currentMove][fromY][fromX];
7870         if(gameMode == EditPosition && piece != EmptySquare &&
7871            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7872             int n;
7873
7874             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7875                 n = PieceToNumber(piece - (int)BlackPawn);
7876                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7877                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7878                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7879             } else
7880             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7881                 n = PieceToNumber(piece);
7882                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7883                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7884                 boards[currentMove][n][BOARD_WIDTH-2]++;
7885             }
7886             boards[currentMove][fromY][fromX] = EmptySquare;
7887         }
7888         ClearHighlights();
7889         fromX = fromY = -1;
7890         MarkTargetSquares(1);
7891         DrawPosition(TRUE, boards[currentMove]);
7892         return;
7893     }
7894
7895     // off-board moves should not be highlighted
7896     if(x < 0 || y < 0) ClearHighlights();
7897     else ReportClick("put", x, y);
7898
7899     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7900
7901     if(legal[toY][toX] == 2) { // highlight-induced promotion
7902         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7903         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7904     }
7905
7906     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7907         SetHighlights(fromX, fromY, toX, toY);
7908         MarkTargetSquares(1);
7909         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7910             // [HGM] super: promotion to captured piece selected from holdings
7911             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7912             promotionChoice = TRUE;
7913             // kludge follows to temporarily execute move on display, without promoting yet
7914             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7915             boards[currentMove][toY][toX] = p;
7916             DrawPosition(FALSE, boards[currentMove]);
7917             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7918             boards[currentMove][toY][toX] = q;
7919             DisplayMessage("Click in holdings to choose piece", "");
7920             return;
7921         }
7922         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7923         PromotionPopUp(promoChoice);
7924     } else {
7925         int oldMove = currentMove;
7926         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7927         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7928         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7929         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7930         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7931            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7932             DrawPosition(TRUE, boards[currentMove]);
7933         fromX = fromY = -1;
7934         flashing = 0;
7935     }
7936     appData.animate = saveAnimate;
7937     if (appData.animate || appData.animateDragging) {
7938         /* Undo animation damage if needed */
7939 //      DrawPosition(FALSE, NULL);
7940     }
7941 }
7942
7943 int
7944 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7945 {   // front-end-free part taken out of PieceMenuPopup
7946     int whichMenu; int xSqr, ySqr;
7947
7948     if(seekGraphUp) { // [HGM] seekgraph
7949         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7950         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7951         return -2;
7952     }
7953
7954     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7955          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7956         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7957         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7958         if(action == Press)   {
7959             originalFlip = flipView;
7960             flipView = !flipView; // temporarily flip board to see game from partners perspective
7961             DrawPosition(TRUE, partnerBoard);
7962             DisplayMessage(partnerStatus, "");
7963             partnerUp = TRUE;
7964         } else if(action == Release) {
7965             flipView = originalFlip;
7966             DrawPosition(TRUE, boards[currentMove]);
7967             partnerUp = FALSE;
7968         }
7969         return -2;
7970     }
7971
7972     xSqr = EventToSquare(x, BOARD_WIDTH);
7973     ySqr = EventToSquare(y, BOARD_HEIGHT);
7974     if (action == Release) {
7975         if(pieceSweep != EmptySquare) {
7976             EditPositionMenuEvent(pieceSweep, toX, toY);
7977             pieceSweep = EmptySquare;
7978         } else UnLoadPV(); // [HGM] pv
7979     }
7980     if (action != Press) return -2; // return code to be ignored
7981     switch (gameMode) {
7982       case IcsExamining:
7983         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7984       case EditPosition:
7985         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7986         if (xSqr < 0 || ySqr < 0) return -1;
7987         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7988         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7989         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7990         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7991         NextPiece(0);
7992         return 2; // grab
7993       case IcsObserving:
7994         if(!appData.icsEngineAnalyze) return -1;
7995       case IcsPlayingWhite:
7996       case IcsPlayingBlack:
7997         if(!appData.zippyPlay) goto noZip;
7998       case AnalyzeMode:
7999       case AnalyzeFile:
8000       case MachinePlaysWhite:
8001       case MachinePlaysBlack:
8002       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8003         if (!appData.dropMenu) {
8004           LoadPV(x, y);
8005           return 2; // flag front-end to grab mouse events
8006         }
8007         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8008            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8009       case EditGame:
8010       noZip:
8011         if (xSqr < 0 || ySqr < 0) return -1;
8012         if (!appData.dropMenu || appData.testLegality &&
8013             gameInfo.variant != VariantBughouse &&
8014             gameInfo.variant != VariantCrazyhouse) return -1;
8015         whichMenu = 1; // drop menu
8016         break;
8017       default:
8018         return -1;
8019     }
8020
8021     if (((*fromX = xSqr) < 0) ||
8022         ((*fromY = ySqr) < 0)) {
8023         *fromX = *fromY = -1;
8024         return -1;
8025     }
8026     if (flipView)
8027       *fromX = BOARD_WIDTH - 1 - *fromX;
8028     else
8029       *fromY = BOARD_HEIGHT - 1 - *fromY;
8030
8031     return whichMenu;
8032 }
8033
8034 void
8035 Wheel (int dir, int x, int y)
8036 {
8037     if(gameMode == EditPosition) {
8038         int xSqr = EventToSquare(x, BOARD_WIDTH);
8039         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8040         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8041         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8042         do {
8043             boards[currentMove][ySqr][xSqr] += dir;
8044             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8045             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8046         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8047         DrawPosition(FALSE, boards[currentMove]);
8048     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8049 }
8050
8051 void
8052 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8053 {
8054 //    char * hint = lastHint;
8055     FrontEndProgramStats stats;
8056
8057     stats.which = cps == &first ? 0 : 1;
8058     stats.depth = cpstats->depth;
8059     stats.nodes = cpstats->nodes;
8060     stats.score = cpstats->score;
8061     stats.time = cpstats->time;
8062     stats.pv = cpstats->movelist;
8063     stats.hint = lastHint;
8064     stats.an_move_index = 0;
8065     stats.an_move_count = 0;
8066
8067     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8068         stats.hint = cpstats->move_name;
8069         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8070         stats.an_move_count = cpstats->nr_moves;
8071     }
8072
8073     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
8074
8075     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8076         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8077
8078     SetProgramStats( &stats );
8079 }
8080
8081 void
8082 ClearEngineOutputPane (int which)
8083 {
8084     static FrontEndProgramStats dummyStats;
8085     dummyStats.which = which;
8086     dummyStats.pv = "#";
8087     SetProgramStats( &dummyStats );
8088 }
8089
8090 #define MAXPLAYERS 500
8091
8092 char *
8093 TourneyStandings (int display)
8094 {
8095     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8096     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8097     char result, *p, *names[MAXPLAYERS];
8098
8099     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8100         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8101     names[0] = p = strdup(appData.participants);
8102     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8103
8104     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8105
8106     while(result = appData.results[nr]) {
8107         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8108         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8109         wScore = bScore = 0;
8110         switch(result) {
8111           case '+': wScore = 2; break;
8112           case '-': bScore = 2; break;
8113           case '=': wScore = bScore = 1; break;
8114           case ' ':
8115           case '*': return strdup("busy"); // tourney not finished
8116         }
8117         score[w] += wScore;
8118         score[b] += bScore;
8119         games[w]++;
8120         games[b]++;
8121         nr++;
8122     }
8123     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8124     for(w=0; w<nPlayers; w++) {
8125         bScore = -1;
8126         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8127         ranking[w] = b; points[w] = bScore; score[b] = -2;
8128     }
8129     p = malloc(nPlayers*34+1);
8130     for(w=0; w<nPlayers && w<display; w++)
8131         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8132     free(names[0]);
8133     return p;
8134 }
8135
8136 void
8137 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8138 {       // count all piece types
8139         int p, f, r;
8140         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8141         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8142         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8143                 p = board[r][f];
8144                 pCnt[p]++;
8145                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8146                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8147                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8148                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8149                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8150                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8151         }
8152 }
8153
8154 int
8155 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8156 {
8157         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8158         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8159
8160         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8161         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8162         if(myPawns == 2 && nMine == 3) // KPP
8163             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8164         if(myPawns == 1 && nMine == 2) // KP
8165             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8166         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8167             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8168         if(myPawns) return FALSE;
8169         if(pCnt[WhiteRook+side])
8170             return pCnt[BlackRook-side] ||
8171                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8172                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8173                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8174         if(pCnt[WhiteCannon+side]) {
8175             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8176             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8177         }
8178         if(pCnt[WhiteKnight+side])
8179             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8180         return FALSE;
8181 }
8182
8183 int
8184 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8185 {
8186         VariantClass v = gameInfo.variant;
8187
8188         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8189         if(v == VariantShatranj) return TRUE; // always winnable through baring
8190         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8191         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8192
8193         if(v == VariantXiangqi) {
8194                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8195
8196                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8197                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8198                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8199                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8200                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8201                 if(stale) // we have at least one last-rank P plus perhaps C
8202                     return majors // KPKX
8203                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8204                 else // KCA*E*
8205                     return pCnt[WhiteFerz+side] // KCAK
8206                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8207                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8208                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8209
8210         } else if(v == VariantKnightmate) {
8211                 if(nMine == 1) return FALSE;
8212                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8213         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8214                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8215
8216                 if(nMine == 1) return FALSE; // bare King
8217                 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
8218                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8219                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8220                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8221                 if(pCnt[WhiteKnight+side])
8222                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8223                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8224                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8225                 if(nBishops)
8226                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8227                 if(pCnt[WhiteAlfil+side])
8228                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8229                 if(pCnt[WhiteWazir+side])
8230                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8231         }
8232
8233         return TRUE;
8234 }
8235
8236 int
8237 CompareWithRights (Board b1, Board b2)
8238 {
8239     int rights = 0;
8240     if(!CompareBoards(b1, b2)) return FALSE;
8241     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8242     /* compare castling rights */
8243     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8244            rights++; /* King lost rights, while rook still had them */
8245     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8246         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8247            rights++; /* but at least one rook lost them */
8248     }
8249     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8250            rights++;
8251     if( b1[CASTLING][5] != NoRights ) {
8252         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8253            rights++;
8254     }
8255     return rights == 0;
8256 }
8257
8258 int
8259 Adjudicate (ChessProgramState *cps)
8260 {       // [HGM] some adjudications useful with buggy engines
8261         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8262         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8263         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8264         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8265         int k, drop, count = 0; static int bare = 1;
8266         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8267         Boolean canAdjudicate = !appData.icsActive;
8268
8269         // most tests only when we understand the game, i.e. legality-checking on
8270             if( appData.testLegality )
8271             {   /* [HGM] Some more adjudications for obstinate engines */
8272                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8273                 static int moveCount = 6;
8274                 ChessMove result;
8275                 char *reason = NULL;
8276
8277                 /* Count what is on board. */
8278                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8279
8280                 /* Some material-based adjudications that have to be made before stalemate test */
8281                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8282                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8283                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8284                      if(canAdjudicate && appData.checkMates) {
8285                          if(engineOpponent)
8286                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8287                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8288                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8289                          return 1;
8290                      }
8291                 }
8292
8293                 /* Bare King in Shatranj (loses) or Losers (wins) */
8294                 if( nrW == 1 || nrB == 1) {
8295                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8296                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8297                      if(canAdjudicate && appData.checkMates) {
8298                          if(engineOpponent)
8299                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8300                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8301                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8302                          return 1;
8303                      }
8304                   } else
8305                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8306                   {    /* bare King */
8307                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8308                         if(canAdjudicate && appData.checkMates) {
8309                             /* but only adjudicate if adjudication enabled */
8310                             if(engineOpponent)
8311                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8312                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8313                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8314                             return 1;
8315                         }
8316                   }
8317                 } else bare = 1;
8318
8319
8320             // don't wait for engine to announce game end if we can judge ourselves
8321             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8322               case MT_CHECK:
8323                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8324                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8325                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8326                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8327                             checkCnt++;
8328                         if(checkCnt >= 2) {
8329                             reason = "Xboard adjudication: 3rd check";
8330                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8331                             break;
8332                         }
8333                     }
8334                 }
8335               case MT_NONE:
8336               default:
8337                 break;
8338               case MT_STEALMATE:
8339               case MT_STALEMATE:
8340               case MT_STAINMATE:
8341                 reason = "Xboard adjudication: Stalemate";
8342                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8343                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8344                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8345                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8346                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8347                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8348                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8349                                                                         EP_CHECKMATE : EP_WINS);
8350                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8351                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8352                 }
8353                 break;
8354               case MT_CHECKMATE:
8355                 reason = "Xboard adjudication: Checkmate";
8356                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8357                 if(gameInfo.variant == VariantShogi) {
8358                     if(forwardMostMove > backwardMostMove
8359                        && moveList[forwardMostMove-1][1] == '@'
8360                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8361                         reason = "XBoard adjudication: pawn-drop mate";
8362                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8363                     }
8364                 }
8365                 break;
8366             }
8367
8368                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8369                     case EP_STALEMATE:
8370                         result = GameIsDrawn; break;
8371                     case EP_CHECKMATE:
8372                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8373                     case EP_WINS:
8374                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8375                     default:
8376                         result = EndOfFile;
8377                 }
8378                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8379                     if(engineOpponent)
8380                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8381                     GameEnds( result, reason, GE_XBOARD );
8382                     return 1;
8383                 }
8384
8385                 /* Next absolutely insufficient mating material. */
8386                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8387                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8388                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8389
8390                      /* always flag draws, for judging claims */
8391                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8392
8393                      if(canAdjudicate && appData.materialDraws) {
8394                          /* but only adjudicate them if adjudication enabled */
8395                          if(engineOpponent) {
8396                            SendToProgram("force\n", engineOpponent); // suppress reply
8397                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8398                          }
8399                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8400                          return 1;
8401                      }
8402                 }
8403
8404                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8405                 if(gameInfo.variant == VariantXiangqi ?
8406                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8407                  : nrW + nrB == 4 &&
8408                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8409                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8410                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8411                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8412                    ) ) {
8413                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8414                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8415                           if(engineOpponent) {
8416                             SendToProgram("force\n", engineOpponent); // suppress reply
8417                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8418                           }
8419                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8420                           return 1;
8421                      }
8422                 } else moveCount = 6;
8423             }
8424
8425         // Repetition draws and 50-move rule can be applied independently of legality testing
8426
8427                 /* Check for rep-draws */
8428                 count = 0;
8429                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8430                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8431                 for(k = forwardMostMove-2;
8432                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8433                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8434                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8435                     k-=2)
8436                 {   int rights=0;
8437                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8438                         /* compare castling rights */
8439                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8440                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8441                                 rights++; /* King lost rights, while rook still had them */
8442                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8443                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8444                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8445                                    rights++; /* but at least one rook lost them */
8446                         }
8447                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8448                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8449                                 rights++;
8450                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8451                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8452                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8453                                    rights++;
8454                         }
8455                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8456                             && appData.drawRepeats > 1) {
8457                              /* adjudicate after user-specified nr of repeats */
8458                              int result = GameIsDrawn;
8459                              char *details = "XBoard adjudication: repetition draw";
8460                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8461                                 // [HGM] xiangqi: check for forbidden perpetuals
8462                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8463                                 for(m=forwardMostMove; m>k; m-=2) {
8464                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8465                                         ourPerpetual = 0; // the current mover did not always check
8466                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8467                                         hisPerpetual = 0; // the opponent did not always check
8468                                 }
8469                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8470                                                                         ourPerpetual, hisPerpetual);
8471                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8472                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8473                                     details = "Xboard adjudication: perpetual checking";
8474                                 } else
8475                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8476                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8477                                 } else
8478                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8479                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8480                                         result = BlackWins;
8481                                         details = "Xboard adjudication: repetition";
8482                                     }
8483                                 } else // it must be XQ
8484                                 // Now check for perpetual chases
8485                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8486                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8487                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8488                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8489                                         static char resdet[MSG_SIZ];
8490                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8491                                         details = resdet;
8492                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8493                                     } else
8494                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8495                                         break; // Abort repetition-checking loop.
8496                                 }
8497                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8498                              }
8499                              if(engineOpponent) {
8500                                SendToProgram("force\n", engineOpponent); // suppress reply
8501                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8502                              }
8503                              GameEnds( result, details, GE_XBOARD );
8504                              return 1;
8505                         }
8506                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8507                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8508                     }
8509                 }
8510
8511                 /* Now we test for 50-move draws. Determine ply count */
8512                 count = forwardMostMove;
8513                 /* look for last irreversble move */
8514                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8515                     count--;
8516                 /* if we hit starting position, add initial plies */
8517                 if( count == backwardMostMove )
8518                     count -= initialRulePlies;
8519                 count = forwardMostMove - count;
8520                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8521                         // adjust reversible move counter for checks in Xiangqi
8522                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8523                         if(i < backwardMostMove) i = backwardMostMove;
8524                         while(i <= forwardMostMove) {
8525                                 lastCheck = inCheck; // check evasion does not count
8526                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8527                                 if(inCheck || lastCheck) count--; // check does not count
8528                                 i++;
8529                         }
8530                 }
8531                 if( count >= 100)
8532                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8533                          /* this is used to judge if draw claims are legal */
8534                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8535                          if(engineOpponent) {
8536                            SendToProgram("force\n", engineOpponent); // suppress reply
8537                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8538                          }
8539                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8540                          return 1;
8541                 }
8542
8543                 /* if draw offer is pending, treat it as a draw claim
8544                  * when draw condition present, to allow engines a way to
8545                  * claim draws before making their move to avoid a race
8546                  * condition occurring after their move
8547                  */
8548                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8549                          char *p = NULL;
8550                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8551                              p = "Draw claim: 50-move rule";
8552                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8553                              p = "Draw claim: 3-fold repetition";
8554                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8555                              p = "Draw claim: insufficient mating material";
8556                          if( p != NULL && canAdjudicate) {
8557                              if(engineOpponent) {
8558                                SendToProgram("force\n", engineOpponent); // suppress reply
8559                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8560                              }
8561                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8562                              return 1;
8563                          }
8564                 }
8565
8566                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8567                     if(engineOpponent) {
8568                       SendToProgram("force\n", engineOpponent); // suppress reply
8569                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8570                     }
8571                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8572                     return 1;
8573                 }
8574         return 0;
8575 }
8576
8577 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8578 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8579 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8580
8581 static int
8582 BitbaseProbe ()
8583 {
8584     int pieces[10], squares[10], cnt=0, r, f, res;
8585     static int loaded;
8586     static PPROBE_EGBB probeBB;
8587     if(!appData.testLegality) return 10;
8588     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8589     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8590     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8591     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8592         ChessSquare piece = boards[forwardMostMove][r][f];
8593         int black = (piece >= BlackPawn);
8594         int type = piece - black*BlackPawn;
8595         if(piece == EmptySquare) continue;
8596         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8597         if(type == WhiteKing) type = WhiteQueen + 1;
8598         type = egbbCode[type];
8599         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8600         pieces[cnt] = type + black*6;
8601         if(++cnt > 5) return 11;
8602     }
8603     pieces[cnt] = squares[cnt] = 0;
8604     // probe EGBB
8605     if(loaded == 2) return 13; // loading failed before
8606     if(loaded == 0) {
8607         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8608         HMODULE lib;
8609         PLOAD_EGBB loadBB;
8610         loaded = 2; // prepare for failure
8611         if(!path) return 13; // no egbb installed
8612         strncpy(buf, path + 8, MSG_SIZ);
8613         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8614         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8615         lib = LoadLibrary(buf);
8616         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8617         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8618         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8619         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8620         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8621         loaded = 1; // success!
8622     }
8623     res = probeBB(forwardMostMove & 1, pieces, squares);
8624     return res > 0 ? 1 : res < 0 ? -1 : 0;
8625 }
8626
8627 char *
8628 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8629 {   // [HGM] book: this routine intercepts moves to simulate book replies
8630     char *bookHit = NULL;
8631
8632     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8633         char buf[MSG_SIZ];
8634         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8635         SendToProgram(buf, cps);
8636     }
8637     //first determine if the incoming move brings opponent into his book
8638     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8639         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8640     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8641     if(bookHit != NULL && !cps->bookSuspend) {
8642         // make sure opponent is not going to reply after receiving move to book position
8643         SendToProgram("force\n", cps);
8644         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8645     }
8646     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8647     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8648     // now arrange restart after book miss
8649     if(bookHit) {
8650         // after a book hit we never send 'go', and the code after the call to this routine
8651         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8652         char buf[MSG_SIZ], *move = bookHit;
8653         if(cps->useSAN) {
8654             int fromX, fromY, toX, toY;
8655             char promoChar;
8656             ChessMove moveType;
8657             move = buf + 30;
8658             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8659                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8660                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8661                                     PosFlags(forwardMostMove),
8662                                     fromY, fromX, toY, toX, promoChar, move);
8663             } else {
8664                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8665                 bookHit = NULL;
8666             }
8667         }
8668         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8669         SendToProgram(buf, cps);
8670         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8671     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8672         SendToProgram("go\n", cps);
8673         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8674     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8675         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8676             SendToProgram("go\n", cps);
8677         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8678     }
8679     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8680 }
8681
8682 int
8683 LoadError (char *errmess, ChessProgramState *cps)
8684 {   // unloads engine and switches back to -ncp mode if it was first
8685     if(cps->initDone) return FALSE;
8686     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8687     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8688     cps->pr = NoProc;
8689     if(cps == &first) {
8690         appData.noChessProgram = TRUE;
8691         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8692         gameMode = BeginningOfGame; ModeHighlight();
8693         SetNCPMode();
8694     }
8695     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8696     DisplayMessage("", ""); // erase waiting message
8697     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8698     return TRUE;
8699 }
8700
8701 char *savedMessage;
8702 ChessProgramState *savedState;
8703 void
8704 DeferredBookMove (void)
8705 {
8706         if(savedState->lastPing != savedState->lastPong)
8707                     ScheduleDelayedEvent(DeferredBookMove, 10);
8708         else
8709         HandleMachineMove(savedMessage, savedState);
8710 }
8711
8712 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8713 static ChessProgramState *stalledEngine;
8714 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8715
8716 void
8717 HandleMachineMove (char *message, ChessProgramState *cps)
8718 {
8719     static char firstLeg[20], legs;
8720     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8721     char realname[MSG_SIZ];
8722     int fromX, fromY, toX, toY;
8723     ChessMove moveType;
8724     char promoChar, roar;
8725     char *p, *pv=buf1;
8726     int oldError;
8727     char *bookHit;
8728
8729     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8730         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8731         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8732             DisplayError(_("Invalid pairing from pairing engine"), 0);
8733             return;
8734         }
8735         pairingReceived = 1;
8736         NextMatchGame();
8737         return; // Skim the pairing messages here.
8738     }
8739
8740     oldError = cps->userError; cps->userError = 0;
8741
8742 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8743     /*
8744      * Kludge to ignore BEL characters
8745      */
8746     while (*message == '\007') message++;
8747
8748     /*
8749      * [HGM] engine debug message: ignore lines starting with '#' character
8750      */
8751     if(cps->debug && *message == '#') return;
8752
8753     /*
8754      * Look for book output
8755      */
8756     if (cps == &first && bookRequested) {
8757         if (message[0] == '\t' || message[0] == ' ') {
8758             /* Part of the book output is here; append it */
8759             strcat(bookOutput, message);
8760             strcat(bookOutput, "  \n");
8761             return;
8762         } else if (bookOutput[0] != NULLCHAR) {
8763             /* All of book output has arrived; display it */
8764             char *p = bookOutput;
8765             while (*p != NULLCHAR) {
8766                 if (*p == '\t') *p = ' ';
8767                 p++;
8768             }
8769             DisplayInformation(bookOutput);
8770             bookRequested = FALSE;
8771             /* Fall through to parse the current output */
8772         }
8773     }
8774
8775     /*
8776      * Look for machine move.
8777      */
8778     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8779         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8780     {
8781         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8782             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8783             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8784             stalledEngine = cps;
8785             if(appData.ponderNextMove) { // bring opponent out of ponder
8786                 if(gameMode == TwoMachinesPlay) {
8787                     if(cps->other->pause)
8788                         PauseEngine(cps->other);
8789                     else
8790                         SendToProgram("easy\n", cps->other);
8791                 }
8792             }
8793             StopClocks();
8794             return;
8795         }
8796
8797       if(cps->usePing) {
8798
8799         /* This method is only useful on engines that support ping */
8800         if(abortEngineThink) {
8801             if (appData.debugMode) {
8802                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8803             }
8804             SendToProgram("undo\n", cps);
8805             return;
8806         }
8807
8808         if (cps->lastPing != cps->lastPong) {
8809             /* Extra move from before last new; ignore */
8810             if (appData.debugMode) {
8811                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8812             }
8813           return;
8814         }
8815
8816       } else {
8817
8818         int machineWhite = FALSE;
8819
8820         switch (gameMode) {
8821           case BeginningOfGame:
8822             /* Extra move from before last reset; ignore */
8823             if (appData.debugMode) {
8824                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8825             }
8826             return;
8827
8828           case EndOfGame:
8829           case IcsIdle:
8830           default:
8831             /* Extra move after we tried to stop.  The mode test is
8832                not a reliable way of detecting this problem, but it's
8833                the best we can do on engines that don't support ping.
8834             */
8835             if (appData.debugMode) {
8836                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8837                         cps->which, gameMode);
8838             }
8839             SendToProgram("undo\n", cps);
8840             return;
8841
8842           case MachinePlaysWhite:
8843           case IcsPlayingWhite:
8844             machineWhite = TRUE;
8845             break;
8846
8847           case MachinePlaysBlack:
8848           case IcsPlayingBlack:
8849             machineWhite = FALSE;
8850             break;
8851
8852           case TwoMachinesPlay:
8853             machineWhite = (cps->twoMachinesColor[0] == 'w');
8854             break;
8855         }
8856         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8857             if (appData.debugMode) {
8858                 fprintf(debugFP,
8859                         "Ignoring move out of turn by %s, gameMode %d"
8860                         ", forwardMost %d\n",
8861                         cps->which, gameMode, forwardMostMove);
8862             }
8863             return;
8864         }
8865       }
8866
8867         if(cps->alphaRank) AlphaRank(machineMove, 4);
8868
8869         // [HGM] lion: (some very limited) support for Alien protocol
8870         killX = killY = kill2X = kill2Y = -1;
8871         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8872             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8873             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8874             return;
8875         }
8876         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8877             char *q = strchr(p+1, ',');            // second comma?
8878             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8879             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8880             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8881         }
8882         if(firstLeg[0]) { // there was a previous leg;
8883             // only support case where same piece makes two step
8884             char buf[20], *p = machineMove+1, *q = buf+1, f;
8885             safeStrCpy(buf, machineMove, 20);
8886             while(isdigit(*q)) q++; // find start of to-square
8887             safeStrCpy(machineMove, firstLeg, 20);
8888             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8889             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
8890             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)
8891             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8892             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8893             firstLeg[0] = NULLCHAR; legs = 0;
8894         }
8895
8896         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8897                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8898             /* Machine move could not be parsed; ignore it. */
8899           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8900                     machineMove, _(cps->which));
8901             DisplayMoveError(buf1);
8902             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8903                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8904             if (gameMode == TwoMachinesPlay) {
8905               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8906                        buf1, GE_XBOARD);
8907             }
8908             return;
8909         }
8910
8911         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8912         /* So we have to redo legality test with true e.p. status here,  */
8913         /* to make sure an illegal e.p. capture does not slip through,   */
8914         /* to cause a forfeit on a justified illegal-move complaint      */
8915         /* of the opponent.                                              */
8916         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8917            ChessMove moveType;
8918            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8919                              fromY, fromX, toY, toX, promoChar);
8920             if(moveType == IllegalMove) {
8921               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8922                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8923                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8924                            buf1, GE_XBOARD);
8925                 return;
8926            } else if(!appData.fischerCastling)
8927            /* [HGM] Kludge to handle engines that send FRC-style castling
8928               when they shouldn't (like TSCP-Gothic) */
8929            switch(moveType) {
8930              case WhiteASideCastleFR:
8931              case BlackASideCastleFR:
8932                toX+=2;
8933                currentMoveString[2]++;
8934                break;
8935              case WhiteHSideCastleFR:
8936              case BlackHSideCastleFR:
8937                toX--;
8938                currentMoveString[2]--;
8939                break;
8940              default: ; // nothing to do, but suppresses warning of pedantic compilers
8941            }
8942         }
8943         hintRequested = FALSE;
8944         lastHint[0] = NULLCHAR;
8945         bookRequested = FALSE;
8946         /* Program may be pondering now */
8947         cps->maybeThinking = TRUE;
8948         if (cps->sendTime == 2) cps->sendTime = 1;
8949         if (cps->offeredDraw) cps->offeredDraw--;
8950
8951         /* [AS] Save move info*/
8952         pvInfoList[ forwardMostMove ].score = programStats.score;
8953         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8954         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8955
8956         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8957
8958         /* Test suites abort the 'game' after one move */
8959         if(*appData.finger) {
8960            static FILE *f;
8961            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8962            if(!f) f = fopen(appData.finger, "w");
8963            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8964            else { DisplayFatalError("Bad output file", errno, 0); return; }
8965            free(fen);
8966            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8967         }
8968         if(appData.epd) {
8969            if(solvingTime >= 0) {
8970               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8971               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8972            } else {
8973               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8974               if(solvingTime == -2) second.matchWins++;
8975            }
8976            OutputKibitz(2, buf1);
8977            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8978         }
8979
8980         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8981         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8982             int count = 0;
8983
8984             while( count < adjudicateLossPlies ) {
8985                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8986
8987                 if( count & 1 ) {
8988                     score = -score; /* Flip score for winning side */
8989                 }
8990
8991                 if( score > appData.adjudicateLossThreshold ) {
8992                     break;
8993                 }
8994
8995                 count++;
8996             }
8997
8998             if( count >= adjudicateLossPlies ) {
8999                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9000
9001                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9002                     "Xboard adjudication",
9003                     GE_XBOARD );
9004
9005                 return;
9006             }
9007         }
9008
9009         if(Adjudicate(cps)) {
9010             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9011             return; // [HGM] adjudicate: for all automatic game ends
9012         }
9013
9014 #if ZIPPY
9015         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9016             first.initDone) {
9017           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9018                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9019                 SendToICS("draw ");
9020                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9021           }
9022           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9023           ics_user_moved = 1;
9024           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9025                 char buf[3*MSG_SIZ];
9026
9027                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9028                         programStats.score / 100.,
9029                         programStats.depth,
9030                         programStats.time / 100.,
9031                         (unsigned int)programStats.nodes,
9032                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9033                         programStats.movelist);
9034                 SendToICS(buf);
9035           }
9036         }
9037 #endif
9038
9039         /* [AS] Clear stats for next move */
9040         ClearProgramStats();
9041         thinkOutput[0] = NULLCHAR;
9042         hiddenThinkOutputState = 0;
9043
9044         bookHit = NULL;
9045         if (gameMode == TwoMachinesPlay) {
9046             /* [HGM] relaying draw offers moved to after reception of move */
9047             /* and interpreting offer as claim if it brings draw condition */
9048             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9049                 SendToProgram("draw\n", cps->other);
9050             }
9051             if (cps->other->sendTime) {
9052                 SendTimeRemaining(cps->other,
9053                                   cps->other->twoMachinesColor[0] == 'w');
9054             }
9055             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9056             if (firstMove && !bookHit) {
9057                 firstMove = FALSE;
9058                 if (cps->other->useColors) {
9059                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9060                 }
9061                 SendToProgram("go\n", cps->other);
9062             }
9063             cps->other->maybeThinking = TRUE;
9064         }
9065
9066         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9067
9068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9069
9070         if (!pausing && appData.ringBellAfterMoves) {
9071             if(!roar) RingBell();
9072         }
9073
9074         /*
9075          * Reenable menu items that were disabled while
9076          * machine was thinking
9077          */
9078         if (gameMode != TwoMachinesPlay)
9079             SetUserThinkingEnables();
9080
9081         // [HGM] book: after book hit opponent has received move and is now in force mode
9082         // force the book reply into it, and then fake that it outputted this move by jumping
9083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9084         if(bookHit) {
9085                 static char bookMove[MSG_SIZ]; // a bit generous?
9086
9087                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9088                 strcat(bookMove, bookHit);
9089                 message = bookMove;
9090                 cps = cps->other;
9091                 programStats.nodes = programStats.depth = programStats.time =
9092                 programStats.score = programStats.got_only_move = 0;
9093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9094
9095                 if(cps->lastPing != cps->lastPong) {
9096                     savedMessage = message; // args for deferred call
9097                     savedState = cps;
9098                     ScheduleDelayedEvent(DeferredBookMove, 10);
9099                     return;
9100                 }
9101                 goto FakeBookMove;
9102         }
9103
9104         return;
9105     }
9106
9107     /* Set special modes for chess engines.  Later something general
9108      *  could be added here; for now there is just one kludge feature,
9109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9110      *  when "xboard" is given as an interactive command.
9111      */
9112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9113         cps->useSigint = FALSE;
9114         cps->useSigterm = FALSE;
9115     }
9116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9117       ParseFeatures(message+8, cps);
9118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9119     }
9120
9121     if (!strncmp(message, "setup ", 6) && 
9122         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9123           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9124                                         ) { // [HGM] allow first engine to define opening position
9125       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9126       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9127       *buf = NULLCHAR;
9128       if(sscanf(message, "setup (%s", buf) == 1) {
9129         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9130         ASSIGN(appData.pieceToCharTable, buf);
9131       }
9132       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9133       if(dummy >= 3) {
9134         while(message[s] && message[s++] != ' ');
9135         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9136            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9137             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9138             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9139             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9140           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9141           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9142           startedFromSetupPosition = FALSE;
9143         }
9144       }
9145       if(startedFromSetupPosition) return;
9146       ParseFEN(boards[0], &dummy, message+s, FALSE);
9147       DrawPosition(TRUE, boards[0]);
9148       CopyBoard(initialPosition, boards[0]);
9149       startedFromSetupPosition = TRUE;
9150       return;
9151     }
9152     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9153       ChessSquare piece = WhitePawn;
9154       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9155       if(*p == '+') promoted++, ID = *++p;
9156       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9157       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9158       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9159       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9160       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9161       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9162       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9163       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9164                                                && gameInfo.variant != VariantGreat
9165                                                && gameInfo.variant != VariantFairy    ) return;
9166       if(piece < EmptySquare) {
9167         pieceDefs = TRUE;
9168         ASSIGN(pieceDesc[piece], buf1);
9169         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9170       }
9171       return;
9172     }
9173     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9174       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9175       Sweep(0);
9176       return;
9177     }
9178     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9179      * want this, I was asked to put it in, and obliged.
9180      */
9181     if (!strncmp(message, "setboard ", 9)) {
9182         Board initial_position;
9183
9184         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9185
9186         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9187             DisplayError(_("Bad FEN received from engine"), 0);
9188             return ;
9189         } else {
9190            Reset(TRUE, FALSE);
9191            CopyBoard(boards[0], initial_position);
9192            initialRulePlies = FENrulePlies;
9193            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9194            else gameMode = MachinePlaysBlack;
9195            DrawPosition(FALSE, boards[currentMove]);
9196         }
9197         return;
9198     }
9199
9200     /*
9201      * Look for communication commands
9202      */
9203     if (!strncmp(message, "telluser ", 9)) {
9204         if(message[9] == '\\' && message[10] == '\\')
9205             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9206         PlayTellSound();
9207         DisplayNote(message + 9);
9208         return;
9209     }
9210     if (!strncmp(message, "tellusererror ", 14)) {
9211         cps->userError = 1;
9212         if(message[14] == '\\' && message[15] == '\\')
9213             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9214         PlayTellSound();
9215         DisplayError(message + 14, 0);
9216         return;
9217     }
9218     if (!strncmp(message, "tellopponent ", 13)) {
9219       if (appData.icsActive) {
9220         if (loggedOn) {
9221           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9222           SendToICS(buf1);
9223         }
9224       } else {
9225         DisplayNote(message + 13);
9226       }
9227       return;
9228     }
9229     if (!strncmp(message, "tellothers ", 11)) {
9230       if (appData.icsActive) {
9231         if (loggedOn) {
9232           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9233           SendToICS(buf1);
9234         }
9235       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9236       return;
9237     }
9238     if (!strncmp(message, "tellall ", 8)) {
9239       if (appData.icsActive) {
9240         if (loggedOn) {
9241           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9242           SendToICS(buf1);
9243         }
9244       } else {
9245         DisplayNote(message + 8);
9246       }
9247       return;
9248     }
9249     if (strncmp(message, "warning", 7) == 0) {
9250         /* Undocumented feature, use tellusererror in new code */
9251         DisplayError(message, 0);
9252         return;
9253     }
9254     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9255         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9256         strcat(realname, " query");
9257         AskQuestion(realname, buf2, buf1, cps->pr);
9258         return;
9259     }
9260     /* Commands from the engine directly to ICS.  We don't allow these to be
9261      *  sent until we are logged on. Crafty kibitzes have been known to
9262      *  interfere with the login process.
9263      */
9264     if (loggedOn) {
9265         if (!strncmp(message, "tellics ", 8)) {
9266             SendToICS(message + 8);
9267             SendToICS("\n");
9268             return;
9269         }
9270         if (!strncmp(message, "tellicsnoalias ", 15)) {
9271             SendToICS(ics_prefix);
9272             SendToICS(message + 15);
9273             SendToICS("\n");
9274             return;
9275         }
9276         /* The following are for backward compatibility only */
9277         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9278             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9279             SendToICS(ics_prefix);
9280             SendToICS(message);
9281             SendToICS("\n");
9282             return;
9283         }
9284     }
9285     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9286         if(initPing == cps->lastPong) {
9287             if(gameInfo.variant == VariantUnknown) {
9288                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9289                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9290                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9291             }
9292             initPing = -1;
9293         }
9294         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9295             abortEngineThink = FALSE;
9296             DisplayMessage("", "");
9297             ThawUI();
9298         }
9299         return;
9300     }
9301     if(!strncmp(message, "highlight ", 10)) {
9302         if(appData.testLegality && !*engineVariant && appData.markers) return;
9303         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9304         return;
9305     }
9306     if(!strncmp(message, "click ", 6)) {
9307         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9308         if(appData.testLegality || !appData.oneClick) return;
9309         sscanf(message+6, "%c%d%c", &f, &y, &c);
9310         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9311         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9312         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9313         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9314         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9315         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9316             LeftClick(Release, lastLeftX, lastLeftY);
9317         controlKey  = (c == ',');
9318         LeftClick(Press, x, y);
9319         LeftClick(Release, x, y);
9320         first.highlight = f;
9321         return;
9322     }
9323     /*
9324      * If the move is illegal, cancel it and redraw the board.
9325      * Also deal with other error cases.  Matching is rather loose
9326      * here to accommodate engines written before the spec.
9327      */
9328     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9329         strncmp(message, "Error", 5) == 0) {
9330         if (StrStr(message, "name") ||
9331             StrStr(message, "rating") || StrStr(message, "?") ||
9332             StrStr(message, "result") || StrStr(message, "board") ||
9333             StrStr(message, "bk") || StrStr(message, "computer") ||
9334             StrStr(message, "variant") || StrStr(message, "hint") ||
9335             StrStr(message, "random") || StrStr(message, "depth") ||
9336             StrStr(message, "accepted")) {
9337             return;
9338         }
9339         if (StrStr(message, "protover")) {
9340           /* Program is responding to input, so it's apparently done
9341              initializing, and this error message indicates it is
9342              protocol version 1.  So we don't need to wait any longer
9343              for it to initialize and send feature commands. */
9344           FeatureDone(cps, 1);
9345           cps->protocolVersion = 1;
9346           return;
9347         }
9348         cps->maybeThinking = FALSE;
9349
9350         if (StrStr(message, "draw")) {
9351             /* Program doesn't have "draw" command */
9352             cps->sendDrawOffers = 0;
9353             return;
9354         }
9355         if (cps->sendTime != 1 &&
9356             (StrStr(message, "time") || StrStr(message, "otim"))) {
9357           /* Program apparently doesn't have "time" or "otim" command */
9358           cps->sendTime = 0;
9359           return;
9360         }
9361         if (StrStr(message, "analyze")) {
9362             cps->analysisSupport = FALSE;
9363             cps->analyzing = FALSE;
9364 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9365             EditGameEvent(); // [HGM] try to preserve loaded game
9366             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9367             DisplayError(buf2, 0);
9368             return;
9369         }
9370         if (StrStr(message, "(no matching move)st")) {
9371           /* Special kludge for GNU Chess 4 only */
9372           cps->stKludge = TRUE;
9373           SendTimeControl(cps, movesPerSession, timeControl,
9374                           timeIncrement, appData.searchDepth,
9375                           searchTime);
9376           return;
9377         }
9378         if (StrStr(message, "(no matching move)sd")) {
9379           /* Special kludge for GNU Chess 4 only */
9380           cps->sdKludge = TRUE;
9381           SendTimeControl(cps, movesPerSession, timeControl,
9382                           timeIncrement, appData.searchDepth,
9383                           searchTime);
9384           return;
9385         }
9386         if (!StrStr(message, "llegal")) {
9387             return;
9388         }
9389         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9390             gameMode == IcsIdle) return;
9391         if (forwardMostMove <= backwardMostMove) return;
9392         if (pausing) PauseEvent();
9393       if(appData.forceIllegal) {
9394             // [HGM] illegal: machine refused move; force position after move into it
9395           SendToProgram("force\n", cps);
9396           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9397                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9398                 // when black is to move, while there might be nothing on a2 or black
9399                 // might already have the move. So send the board as if white has the move.
9400                 // But first we must change the stm of the engine, as it refused the last move
9401                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9402                 if(WhiteOnMove(forwardMostMove)) {
9403                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9404                     SendBoard(cps, forwardMostMove); // kludgeless board
9405                 } else {
9406                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9407                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9408                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9409                 }
9410           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9411             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9412                  gameMode == TwoMachinesPlay)
9413               SendToProgram("go\n", cps);
9414             return;
9415       } else
9416         if (gameMode == PlayFromGameFile) {
9417             /* Stop reading this game file */
9418             gameMode = EditGame;
9419             ModeHighlight();
9420         }
9421         /* [HGM] illegal-move claim should forfeit game when Xboard */
9422         /* only passes fully legal moves                            */
9423         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9424             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9425                                 "False illegal-move claim", GE_XBOARD );
9426             return; // do not take back move we tested as valid
9427         }
9428         currentMove = forwardMostMove-1;
9429         DisplayMove(currentMove-1); /* before DisplayMoveError */
9430         SwitchClocks(forwardMostMove-1); // [HGM] race
9431         DisplayBothClocks();
9432         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9433                 parseList[currentMove], _(cps->which));
9434         DisplayMoveError(buf1);
9435         DrawPosition(FALSE, boards[currentMove]);
9436
9437         SetUserThinkingEnables();
9438         return;
9439     }
9440     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9441         /* Program has a broken "time" command that
9442            outputs a string not ending in newline.
9443            Don't use it. */
9444         cps->sendTime = 0;
9445     }
9446     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9447         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9448             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9449     }
9450
9451     /*
9452      * If chess program startup fails, exit with an error message.
9453      * Attempts to recover here are futile. [HGM] Well, we try anyway
9454      */
9455     if ((StrStr(message, "unknown host") != NULL)
9456         || (StrStr(message, "No remote directory") != NULL)
9457         || (StrStr(message, "not found") != NULL)
9458         || (StrStr(message, "No such file") != NULL)
9459         || (StrStr(message, "can't alloc") != NULL)
9460         || (StrStr(message, "Permission denied") != NULL)) {
9461
9462         cps->maybeThinking = FALSE;
9463         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9464                 _(cps->which), cps->program, cps->host, message);
9465         RemoveInputSource(cps->isr);
9466         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9467             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9468             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9469         }
9470         return;
9471     }
9472
9473     /*
9474      * Look for hint output
9475      */
9476     if (sscanf(message, "Hint: %s", buf1) == 1) {
9477         if (cps == &first && hintRequested) {
9478             hintRequested = FALSE;
9479             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9480                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9481                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9482                                     PosFlags(forwardMostMove),
9483                                     fromY, fromX, toY, toX, promoChar, buf1);
9484                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9485                 DisplayInformation(buf2);
9486             } else {
9487                 /* Hint move could not be parsed!? */
9488               snprintf(buf2, sizeof(buf2),
9489                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9490                         buf1, _(cps->which));
9491                 DisplayError(buf2, 0);
9492             }
9493         } else {
9494           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9495         }
9496         return;
9497     }
9498
9499     /*
9500      * Ignore other messages if game is not in progress
9501      */
9502     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9503         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9504
9505     /*
9506      * look for win, lose, draw, or draw offer
9507      */
9508     if (strncmp(message, "1-0", 3) == 0) {
9509         char *p, *q, *r = "";
9510         p = strchr(message, '{');
9511         if (p) {
9512             q = strchr(p, '}');
9513             if (q) {
9514                 *q = NULLCHAR;
9515                 r = p + 1;
9516             }
9517         }
9518         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9519         return;
9520     } else if (strncmp(message, "0-1", 3) == 0) {
9521         char *p, *q, *r = "";
9522         p = strchr(message, '{');
9523         if (p) {
9524             q = strchr(p, '}');
9525             if (q) {
9526                 *q = NULLCHAR;
9527                 r = p + 1;
9528             }
9529         }
9530         /* Kludge for Arasan 4.1 bug */
9531         if (strcmp(r, "Black resigns") == 0) {
9532             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9533             return;
9534         }
9535         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9536         return;
9537     } else if (strncmp(message, "1/2", 3) == 0) {
9538         char *p, *q, *r = "";
9539         p = strchr(message, '{');
9540         if (p) {
9541             q = strchr(p, '}');
9542             if (q) {
9543                 *q = NULLCHAR;
9544                 r = p + 1;
9545             }
9546         }
9547
9548         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9549         return;
9550
9551     } else if (strncmp(message, "White resign", 12) == 0) {
9552         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9553         return;
9554     } else if (strncmp(message, "Black resign", 12) == 0) {
9555         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9556         return;
9557     } else if (strncmp(message, "White matches", 13) == 0 ||
9558                strncmp(message, "Black matches", 13) == 0   ) {
9559         /* [HGM] ignore GNUShogi noises */
9560         return;
9561     } else if (strncmp(message, "White", 5) == 0 &&
9562                message[5] != '(' &&
9563                StrStr(message, "Black") == NULL) {
9564         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9565         return;
9566     } else if (strncmp(message, "Black", 5) == 0 &&
9567                message[5] != '(') {
9568         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9569         return;
9570     } else if (strcmp(message, "resign") == 0 ||
9571                strcmp(message, "computer resigns") == 0) {
9572         switch (gameMode) {
9573           case MachinePlaysBlack:
9574           case IcsPlayingBlack:
9575             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9576             break;
9577           case MachinePlaysWhite:
9578           case IcsPlayingWhite:
9579             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9580             break;
9581           case TwoMachinesPlay:
9582             if (cps->twoMachinesColor[0] == 'w')
9583               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9584             else
9585               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9586             break;
9587           default:
9588             /* can't happen */
9589             break;
9590         }
9591         return;
9592     } else if (strncmp(message, "opponent mates", 14) == 0) {
9593         switch (gameMode) {
9594           case MachinePlaysBlack:
9595           case IcsPlayingBlack:
9596             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9597             break;
9598           case MachinePlaysWhite:
9599           case IcsPlayingWhite:
9600             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9601             break;
9602           case TwoMachinesPlay:
9603             if (cps->twoMachinesColor[0] == 'w')
9604               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9605             else
9606               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9607             break;
9608           default:
9609             /* can't happen */
9610             break;
9611         }
9612         return;
9613     } else if (strncmp(message, "computer mates", 14) == 0) {
9614         switch (gameMode) {
9615           case MachinePlaysBlack:
9616           case IcsPlayingBlack:
9617             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9618             break;
9619           case MachinePlaysWhite:
9620           case IcsPlayingWhite:
9621             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9622             break;
9623           case TwoMachinesPlay:
9624             if (cps->twoMachinesColor[0] == 'w')
9625               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9626             else
9627               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9628             break;
9629           default:
9630             /* can't happen */
9631             break;
9632         }
9633         return;
9634     } else if (strncmp(message, "checkmate", 9) == 0) {
9635         if (WhiteOnMove(forwardMostMove)) {
9636             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9637         } else {
9638             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9639         }
9640         return;
9641     } else if (strstr(message, "Draw") != NULL ||
9642                strstr(message, "game is a draw") != NULL) {
9643         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9644         return;
9645     } else if (strstr(message, "offer") != NULL &&
9646                strstr(message, "draw") != NULL) {
9647 #if ZIPPY
9648         if (appData.zippyPlay && first.initDone) {
9649             /* Relay offer to ICS */
9650             SendToICS(ics_prefix);
9651             SendToICS("draw\n");
9652         }
9653 #endif
9654         cps->offeredDraw = 2; /* valid until this engine moves twice */
9655         if (gameMode == TwoMachinesPlay) {
9656             if (cps->other->offeredDraw) {
9657                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9658             /* [HGM] in two-machine mode we delay relaying draw offer      */
9659             /* until after we also have move, to see if it is really claim */
9660             }
9661         } else if (gameMode == MachinePlaysWhite ||
9662                    gameMode == MachinePlaysBlack) {
9663           if (userOfferedDraw) {
9664             DisplayInformation(_("Machine accepts your draw offer"));
9665             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9666           } else {
9667             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9668           }
9669         }
9670     }
9671
9672
9673     /*
9674      * Look for thinking output
9675      */
9676     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9677           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9678                                 ) {
9679         int plylev, mvleft, mvtot, curscore, time;
9680         char mvname[MOVE_LEN];
9681         u64 nodes; // [DM]
9682         char plyext;
9683         int ignore = FALSE;
9684         int prefixHint = FALSE;
9685         mvname[0] = NULLCHAR;
9686
9687         switch (gameMode) {
9688           case MachinePlaysBlack:
9689           case IcsPlayingBlack:
9690             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9691             break;
9692           case MachinePlaysWhite:
9693           case IcsPlayingWhite:
9694             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9695             break;
9696           case AnalyzeMode:
9697           case AnalyzeFile:
9698             break;
9699           case IcsObserving: /* [DM] icsEngineAnalyze */
9700             if (!appData.icsEngineAnalyze) ignore = TRUE;
9701             break;
9702           case TwoMachinesPlay:
9703             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9704                 ignore = TRUE;
9705             }
9706             break;
9707           default:
9708             ignore = TRUE;
9709             break;
9710         }
9711
9712         if (!ignore) {
9713             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9714             int solved = 0;
9715             buf1[0] = NULLCHAR;
9716             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9717                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9718                 char score_buf[MSG_SIZ];
9719
9720                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9721                     nodes += u64Const(0x100000000);
9722
9723                 if (plyext != ' ' && plyext != '\t') {
9724                     time *= 100;
9725                 }
9726
9727                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9728                 if( cps->scoreIsAbsolute &&
9729                     ( gameMode == MachinePlaysBlack ||
9730                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9731                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9732                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9733                      !WhiteOnMove(currentMove)
9734                     ) )
9735                 {
9736                     curscore = -curscore;
9737                 }
9738
9739                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9740
9741                 if(*bestMove) { // rememer time best EPD move was first found
9742                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9743                     ChessMove mt; char *p = bestMove;
9744                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9745                     solved = 0;
9746                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9747                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9748                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9749                             solved = 1;
9750                             break;
9751                         }
9752                         while(*p && *p != ' ') p++;
9753                         while(*p == ' ') p++;
9754                     }
9755                     if(!solved) solvingTime = -1;
9756                 }
9757                 if(*avoidMove && !solved) {
9758                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9759                     ChessMove mt; char *p = avoidMove, solved = 1;
9760                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9761                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9762                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9763                             solved = 0; solvingTime = -2;
9764                             break;
9765                         }
9766                         while(*p && *p != ' ') p++;
9767                         while(*p == ' ') p++;
9768                     }
9769                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9770                 }
9771
9772                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9773                         char buf[MSG_SIZ];
9774                         FILE *f;
9775                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9776                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9777                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9778                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9779                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9780                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9781                                 fclose(f);
9782                         }
9783                         else
9784                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9785                           DisplayError(_("failed writing PV"), 0);
9786                 }
9787
9788                 tempStats.depth = plylev;
9789                 tempStats.nodes = nodes;
9790                 tempStats.time = time;
9791                 tempStats.score = curscore;
9792                 tempStats.got_only_move = 0;
9793
9794                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9795                         int ticklen;
9796
9797                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9798                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9799                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9800                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9801                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9802                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9803                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9804                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9805                 }
9806
9807                 /* Buffer overflow protection */
9808                 if (pv[0] != NULLCHAR) {
9809                     if (strlen(pv) >= sizeof(tempStats.movelist)
9810                         && appData.debugMode) {
9811                         fprintf(debugFP,
9812                                 "PV is too long; using the first %u bytes.\n",
9813                                 (unsigned) sizeof(tempStats.movelist) - 1);
9814                     }
9815
9816                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9817                 } else {
9818                     sprintf(tempStats.movelist, " no PV\n");
9819                 }
9820
9821                 if (tempStats.seen_stat) {
9822                     tempStats.ok_to_send = 1;
9823                 }
9824
9825                 if (strchr(tempStats.movelist, '(') != NULL) {
9826                     tempStats.line_is_book = 1;
9827                     tempStats.nr_moves = 0;
9828                     tempStats.moves_left = 0;
9829                 } else {
9830                     tempStats.line_is_book = 0;
9831                 }
9832
9833                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9834                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9835
9836                 SendProgramStatsToFrontend( cps, &tempStats );
9837
9838                 /*
9839                     [AS] Protect the thinkOutput buffer from overflow... this
9840                     is only useful if buf1 hasn't overflowed first!
9841                 */
9842                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9843                 if(curscore >= MATE_SCORE) 
9844                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9845                 else if(curscore <= -MATE_SCORE) 
9846                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9847                 else
9848                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9849                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9850                          plylev,
9851                          (gameMode == TwoMachinesPlay ?
9852                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9853                          score_buf,
9854                          prefixHint ? lastHint : "",
9855                          prefixHint ? " " : "" );
9856
9857                 if( buf1[0] != NULLCHAR ) {
9858                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9859
9860                     if( strlen(pv) > max_len ) {
9861                         if( appData.debugMode) {
9862                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9863                         }
9864                         pv[max_len+1] = '\0';
9865                     }
9866
9867                     strcat( thinkOutput, pv);
9868                 }
9869
9870                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9871                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9872                     DisplayMove(currentMove - 1);
9873                 }
9874                 return;
9875
9876             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9877                 /* crafty (9.25+) says "(only move) <move>"
9878                  * if there is only 1 legal move
9879                  */
9880                 sscanf(p, "(only move) %s", buf1);
9881                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9882                 sprintf(programStats.movelist, "%s (only move)", buf1);
9883                 programStats.depth = 1;
9884                 programStats.nr_moves = 1;
9885                 programStats.moves_left = 1;
9886                 programStats.nodes = 1;
9887                 programStats.time = 1;
9888                 programStats.got_only_move = 1;
9889
9890                 /* Not really, but we also use this member to
9891                    mean "line isn't going to change" (Crafty
9892                    isn't searching, so stats won't change) */
9893                 programStats.line_is_book = 1;
9894
9895                 SendProgramStatsToFrontend( cps, &programStats );
9896
9897                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9898                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9899                     DisplayMove(currentMove - 1);
9900                 }
9901                 return;
9902             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9903                               &time, &nodes, &plylev, &mvleft,
9904                               &mvtot, mvname) >= 5) {
9905                 /* The stat01: line is from Crafty (9.29+) in response
9906                    to the "." command */
9907                 programStats.seen_stat = 1;
9908                 cps->maybeThinking = TRUE;
9909
9910                 if (programStats.got_only_move || !appData.periodicUpdates)
9911                   return;
9912
9913                 programStats.depth = plylev;
9914                 programStats.time = time;
9915                 programStats.nodes = nodes;
9916                 programStats.moves_left = mvleft;
9917                 programStats.nr_moves = mvtot;
9918                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9919                 programStats.ok_to_send = 1;
9920                 programStats.movelist[0] = '\0';
9921
9922                 SendProgramStatsToFrontend( cps, &programStats );
9923
9924                 return;
9925
9926             } else if (strncmp(message,"++",2) == 0) {
9927                 /* Crafty 9.29+ outputs this */
9928                 programStats.got_fail = 2;
9929                 return;
9930
9931             } else if (strncmp(message,"--",2) == 0) {
9932                 /* Crafty 9.29+ outputs this */
9933                 programStats.got_fail = 1;
9934                 return;
9935
9936             } else if (thinkOutput[0] != NULLCHAR &&
9937                        strncmp(message, "    ", 4) == 0) {
9938                 unsigned message_len;
9939
9940                 p = message;
9941                 while (*p && *p == ' ') p++;
9942
9943                 message_len = strlen( p );
9944
9945                 /* [AS] Avoid buffer overflow */
9946                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9947                     strcat(thinkOutput, " ");
9948                     strcat(thinkOutput, p);
9949                 }
9950
9951                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9952                     strcat(programStats.movelist, " ");
9953                     strcat(programStats.movelist, p);
9954                 }
9955
9956                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9957                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9958                     DisplayMove(currentMove - 1);
9959                 }
9960                 return;
9961             }
9962         }
9963         else {
9964             buf1[0] = NULLCHAR;
9965
9966             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9967                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9968             {
9969                 ChessProgramStats cpstats;
9970
9971                 if (plyext != ' ' && plyext != '\t') {
9972                     time *= 100;
9973                 }
9974
9975                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9976                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9977                     curscore = -curscore;
9978                 }
9979
9980                 cpstats.depth = plylev;
9981                 cpstats.nodes = nodes;
9982                 cpstats.time = time;
9983                 cpstats.score = curscore;
9984                 cpstats.got_only_move = 0;
9985                 cpstats.movelist[0] = '\0';
9986
9987                 if (buf1[0] != NULLCHAR) {
9988                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9989                 }
9990
9991                 cpstats.ok_to_send = 0;
9992                 cpstats.line_is_book = 0;
9993                 cpstats.nr_moves = 0;
9994                 cpstats.moves_left = 0;
9995
9996                 SendProgramStatsToFrontend( cps, &cpstats );
9997             }
9998         }
9999     }
10000 }
10001
10002
10003 /* Parse a game score from the character string "game", and
10004    record it as the history of the current game.  The game
10005    score is NOT assumed to start from the standard position.
10006    The display is not updated in any way.
10007    */
10008 void
10009 ParseGameHistory (char *game)
10010 {
10011     ChessMove moveType;
10012     int fromX, fromY, toX, toY, boardIndex;
10013     char promoChar;
10014     char *p, *q;
10015     char buf[MSG_SIZ];
10016
10017     if (appData.debugMode)
10018       fprintf(debugFP, "Parsing game history: %s\n", game);
10019
10020     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10021     gameInfo.site = StrSave(appData.icsHost);
10022     gameInfo.date = PGNDate();
10023     gameInfo.round = StrSave("-");
10024
10025     /* Parse out names of players */
10026     while (*game == ' ') game++;
10027     p = buf;
10028     while (*game != ' ') *p++ = *game++;
10029     *p = NULLCHAR;
10030     gameInfo.white = StrSave(buf);
10031     while (*game == ' ') game++;
10032     p = buf;
10033     while (*game != ' ' && *game != '\n') *p++ = *game++;
10034     *p = NULLCHAR;
10035     gameInfo.black = StrSave(buf);
10036
10037     /* Parse moves */
10038     boardIndex = blackPlaysFirst ? 1 : 0;
10039     yynewstr(game);
10040     for (;;) {
10041         yyboardindex = boardIndex;
10042         moveType = (ChessMove) Myylex();
10043         switch (moveType) {
10044           case IllegalMove:             /* maybe suicide chess, etc. */
10045   if (appData.debugMode) {
10046     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10047     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10048     setbuf(debugFP, NULL);
10049   }
10050           case WhitePromotion:
10051           case BlackPromotion:
10052           case WhiteNonPromotion:
10053           case BlackNonPromotion:
10054           case NormalMove:
10055           case FirstLeg:
10056           case WhiteCapturesEnPassant:
10057           case BlackCapturesEnPassant:
10058           case WhiteKingSideCastle:
10059           case WhiteQueenSideCastle:
10060           case BlackKingSideCastle:
10061           case BlackQueenSideCastle:
10062           case WhiteKingSideCastleWild:
10063           case WhiteQueenSideCastleWild:
10064           case BlackKingSideCastleWild:
10065           case BlackQueenSideCastleWild:
10066           /* PUSH Fabien */
10067           case WhiteHSideCastleFR:
10068           case WhiteASideCastleFR:
10069           case BlackHSideCastleFR:
10070           case BlackASideCastleFR:
10071           /* POP Fabien */
10072             fromX = currentMoveString[0] - AAA;
10073             fromY = currentMoveString[1] - ONE;
10074             toX = currentMoveString[2] - AAA;
10075             toY = currentMoveString[3] - ONE;
10076             promoChar = currentMoveString[4];
10077             break;
10078           case WhiteDrop:
10079           case BlackDrop:
10080             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10081             fromX = moveType == WhiteDrop ?
10082               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10083             (int) CharToPiece(ToLower(currentMoveString[0]));
10084             fromY = DROP_RANK;
10085             toX = currentMoveString[2] - AAA;
10086             toY = currentMoveString[3] - ONE;
10087             promoChar = NULLCHAR;
10088             break;
10089           case AmbiguousMove:
10090             /* bug? */
10091             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10092   if (appData.debugMode) {
10093     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10094     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10095     setbuf(debugFP, NULL);
10096   }
10097             DisplayError(buf, 0);
10098             return;
10099           case ImpossibleMove:
10100             /* bug? */
10101             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10102   if (appData.debugMode) {
10103     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10104     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10105     setbuf(debugFP, NULL);
10106   }
10107             DisplayError(buf, 0);
10108             return;
10109           case EndOfFile:
10110             if (boardIndex < backwardMostMove) {
10111                 /* Oops, gap.  How did that happen? */
10112                 DisplayError(_("Gap in move list"), 0);
10113                 return;
10114             }
10115             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10116             if (boardIndex > forwardMostMove) {
10117                 forwardMostMove = boardIndex;
10118             }
10119             return;
10120           case ElapsedTime:
10121             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10122                 strcat(parseList[boardIndex-1], " ");
10123                 strcat(parseList[boardIndex-1], yy_text);
10124             }
10125             continue;
10126           case Comment:
10127           case PGNTag:
10128           case NAG:
10129           default:
10130             /* ignore */
10131             continue;
10132           case WhiteWins:
10133           case BlackWins:
10134           case GameIsDrawn:
10135           case GameUnfinished:
10136             if (gameMode == IcsExamining) {
10137                 if (boardIndex < backwardMostMove) {
10138                     /* Oops, gap.  How did that happen? */
10139                     return;
10140                 }
10141                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10142                 return;
10143             }
10144             gameInfo.result = moveType;
10145             p = strchr(yy_text, '{');
10146             if (p == NULL) p = strchr(yy_text, '(');
10147             if (p == NULL) {
10148                 p = yy_text;
10149                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10150             } else {
10151                 q = strchr(p, *p == '{' ? '}' : ')');
10152                 if (q != NULL) *q = NULLCHAR;
10153                 p++;
10154             }
10155             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10156             gameInfo.resultDetails = StrSave(p);
10157             continue;
10158         }
10159         if (boardIndex >= forwardMostMove &&
10160             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10161             backwardMostMove = blackPlaysFirst ? 1 : 0;
10162             return;
10163         }
10164         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10165                                  fromY, fromX, toY, toX, promoChar,
10166                                  parseList[boardIndex]);
10167         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10168         /* currentMoveString is set as a side-effect of yylex */
10169         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10170         strcat(moveList[boardIndex], "\n");
10171         boardIndex++;
10172         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10173         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10174           case MT_NONE:
10175           case MT_STALEMATE:
10176           default:
10177             break;
10178           case MT_CHECK:
10179             if(!IS_SHOGI(gameInfo.variant))
10180                 strcat(parseList[boardIndex - 1], "+");
10181             break;
10182           case MT_CHECKMATE:
10183           case MT_STAINMATE:
10184             strcat(parseList[boardIndex - 1], "#");
10185             break;
10186         }
10187     }
10188 }
10189
10190
10191 /* Apply a move to the given board  */
10192 void
10193 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10194 {
10195   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10196   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10197
10198     /* [HGM] compute & store e.p. status and castling rights for new position */
10199     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10200
10201       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10202       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10203       board[EP_STATUS] = EP_NONE;
10204       board[EP_FILE] = board[EP_RANK] = 100;
10205
10206   if (fromY == DROP_RANK) {
10207         /* must be first */
10208         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10209             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10210             return;
10211         }
10212         piece = board[toY][toX] = (ChessSquare) fromX;
10213   } else {
10214 //      ChessSquare victim;
10215       int i;
10216
10217       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10218 //           victim = board[killY][killX],
10219            killed = board[killY][killX],
10220            board[killY][killX] = EmptySquare,
10221            board[EP_STATUS] = EP_CAPTURE;
10222            if( kill2X >= 0 && kill2Y >= 0)
10223              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10224       }
10225
10226       if( board[toY][toX] != EmptySquare ) {
10227            board[EP_STATUS] = EP_CAPTURE;
10228            if( (fromX != toX || fromY != toY) && // not igui!
10229                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10230                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10231                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10232            }
10233       }
10234
10235       pawn = board[fromY][fromX];
10236       if( pawn == WhiteLance || pawn == BlackLance ) {
10237            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10238                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10239                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10240            }
10241       }
10242       if( pawn == WhitePawn ) {
10243            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10244                board[EP_STATUS] = EP_PAWN_MOVE;
10245            if( toY-fromY>=2) {
10246                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10247                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10248                         gameInfo.variant != VariantBerolina || toX < fromX)
10249                       board[EP_STATUS] = toX | berolina;
10250                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10251                         gameInfo.variant != VariantBerolina || toX > fromX)
10252                       board[EP_STATUS] = toX;
10253            }
10254       } else
10255       if( pawn == BlackPawn ) {
10256            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10257                board[EP_STATUS] = EP_PAWN_MOVE;
10258            if( toY-fromY<= -2) {
10259                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10260                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10261                         gameInfo.variant != VariantBerolina || toX < fromX)
10262                       board[EP_STATUS] = toX | berolina;
10263                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10264                         gameInfo.variant != VariantBerolina || toX > fromX)
10265                       board[EP_STATUS] = toX;
10266            }
10267        }
10268
10269        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10270        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10271        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10272        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10273
10274        for(i=0; i<nrCastlingRights; i++) {
10275            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10276               board[CASTLING][i] == toX   && castlingRank[i] == toY
10277              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10278        }
10279
10280        if(gameInfo.variant == VariantSChess) { // update virginity
10281            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10282            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10283            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10284            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10285        }
10286
10287      if (fromX == toX && fromY == toY && killX < 0) return;
10288
10289      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10290      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10291      if(gameInfo.variant == VariantKnightmate)
10292          king += (int) WhiteUnicorn - (int) WhiteKing;
10293
10294     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10295        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10296         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10297         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10298         board[EP_STATUS] = EP_NONE; // capture was fake!
10299     } else
10300     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10301         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10302         board[toY][toX] = piece;
10303         board[EP_STATUS] = EP_NONE; // capture was fake!
10304     } else
10305     /* Code added by Tord: */
10306     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10307     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10308         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10309       board[EP_STATUS] = EP_NONE; // capture was fake!
10310       board[fromY][fromX] = EmptySquare;
10311       board[toY][toX] = EmptySquare;
10312       if((toX > fromX) != (piece == WhiteRook)) {
10313         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10314       } else {
10315         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10316       }
10317     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10318                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10319       board[EP_STATUS] = EP_NONE;
10320       board[fromY][fromX] = EmptySquare;
10321       board[toY][toX] = EmptySquare;
10322       if((toX > fromX) != (piece == BlackRook)) {
10323         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10324       } else {
10325         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10326       }
10327     /* End of code added by Tord */
10328
10329     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10330         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10331         board[toY][toX] = piece;
10332     } else if (board[fromY][fromX] == king
10333         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10334         && toY == fromY && toX > fromX+1) {
10335         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10336                                                                                              ; // castle with nearest piece
10337         board[fromY][toX-1] = board[fromY][rookX];
10338         board[fromY][rookX] = EmptySquare;
10339         board[fromY][fromX] = EmptySquare;
10340         board[toY][toX] = king;
10341     } else if (board[fromY][fromX] == king
10342         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10343                && toY == fromY && toX < fromX-1) {
10344         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10345                                                                                   ; // castle with nearest piece
10346         board[fromY][toX+1] = board[fromY][rookX];
10347         board[fromY][rookX] = EmptySquare;
10348         board[fromY][fromX] = EmptySquare;
10349         board[toY][toX] = king;
10350     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10351                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10352                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10353                ) {
10354         /* white pawn promotion */
10355         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10356         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10357             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10358         board[fromY][fromX] = EmptySquare;
10359     } else if ((fromY >= BOARD_HEIGHT>>1)
10360                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10361                && (toX != fromX)
10362                && gameInfo.variant != VariantXiangqi
10363                && gameInfo.variant != VariantBerolina
10364                && (pawn == WhitePawn)
10365                && (board[toY][toX] == EmptySquare)) {
10366         board[fromY][fromX] = EmptySquare;
10367         board[toY][toX] = piece;
10368         if(toY == epRank - 128 + 1)
10369             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10370         else
10371             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10372     } else if ((fromY == BOARD_HEIGHT-4)
10373                && (toX == fromX)
10374                && gameInfo.variant == VariantBerolina
10375                && (board[fromY][fromX] == WhitePawn)
10376                && (board[toY][toX] == EmptySquare)) {
10377         board[fromY][fromX] = EmptySquare;
10378         board[toY][toX] = WhitePawn;
10379         if(oldEP & EP_BEROLIN_A) {
10380                 captured = board[fromY][fromX-1];
10381                 board[fromY][fromX-1] = EmptySquare;
10382         }else{  captured = board[fromY][fromX+1];
10383                 board[fromY][fromX+1] = EmptySquare;
10384         }
10385     } else if (board[fromY][fromX] == king
10386         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10387                && toY == fromY && toX > fromX+1) {
10388         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10389                                                                                              ;
10390         board[fromY][toX-1] = board[fromY][rookX];
10391         board[fromY][rookX] = EmptySquare;
10392         board[fromY][fromX] = EmptySquare;
10393         board[toY][toX] = king;
10394     } else if (board[fromY][fromX] == king
10395         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10396                && toY == fromY && toX < fromX-1) {
10397         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10398                                                                                 ;
10399         board[fromY][toX+1] = board[fromY][rookX];
10400         board[fromY][rookX] = EmptySquare;
10401         board[fromY][fromX] = EmptySquare;
10402         board[toY][toX] = king;
10403     } else if (fromY == 7 && fromX == 3
10404                && board[fromY][fromX] == BlackKing
10405                && toY == 7 && toX == 5) {
10406         board[fromY][fromX] = EmptySquare;
10407         board[toY][toX] = BlackKing;
10408         board[fromY][7] = EmptySquare;
10409         board[toY][4] = BlackRook;
10410     } else if (fromY == 7 && fromX == 3
10411                && board[fromY][fromX] == BlackKing
10412                && toY == 7 && toX == 1) {
10413         board[fromY][fromX] = EmptySquare;
10414         board[toY][toX] = BlackKing;
10415         board[fromY][0] = EmptySquare;
10416         board[toY][2] = BlackRook;
10417     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10418                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10419                && toY < promoRank && promoChar
10420                ) {
10421         /* black pawn promotion */
10422         board[toY][toX] = CharToPiece(ToLower(promoChar));
10423         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10424             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10425         board[fromY][fromX] = EmptySquare;
10426     } else if ((fromY < BOARD_HEIGHT>>1)
10427                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10428                && (toX != fromX)
10429                && gameInfo.variant != VariantXiangqi
10430                && gameInfo.variant != VariantBerolina
10431                && (pawn == BlackPawn)
10432                && (board[toY][toX] == EmptySquare)) {
10433         board[fromY][fromX] = EmptySquare;
10434         board[toY][toX] = piece;
10435         if(toY == epRank - 128 - 1)
10436             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10437         else
10438             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10439     } else if ((fromY == 3)
10440                && (toX == fromX)
10441                && gameInfo.variant == VariantBerolina
10442                && (board[fromY][fromX] == BlackPawn)
10443                && (board[toY][toX] == EmptySquare)) {
10444         board[fromY][fromX] = EmptySquare;
10445         board[toY][toX] = BlackPawn;
10446         if(oldEP & EP_BEROLIN_A) {
10447                 captured = board[fromY][fromX-1];
10448                 board[fromY][fromX-1] = EmptySquare;
10449         }else{  captured = board[fromY][fromX+1];
10450                 board[fromY][fromX+1] = EmptySquare;
10451         }
10452     } else {
10453         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10454         board[fromY][fromX] = EmptySquare;
10455         board[toY][toX] = piece;
10456     }
10457   }
10458
10459     if (gameInfo.holdingsWidth != 0) {
10460
10461       /* !!A lot more code needs to be written to support holdings  */
10462       /* [HGM] OK, so I have written it. Holdings are stored in the */
10463       /* penultimate board files, so they are automaticlly stored   */
10464       /* in the game history.                                       */
10465       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10466                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10467         /* Delete from holdings, by decreasing count */
10468         /* and erasing image if necessary            */
10469         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10470         if(p < (int) BlackPawn) { /* white drop */
10471              p -= (int)WhitePawn;
10472                  p = PieceToNumber((ChessSquare)p);
10473              if(p >= gameInfo.holdingsSize) p = 0;
10474              if(--board[p][BOARD_WIDTH-2] <= 0)
10475                   board[p][BOARD_WIDTH-1] = EmptySquare;
10476              if((int)board[p][BOARD_WIDTH-2] < 0)
10477                         board[p][BOARD_WIDTH-2] = 0;
10478         } else {                  /* black drop */
10479              p -= (int)BlackPawn;
10480                  p = PieceToNumber((ChessSquare)p);
10481              if(p >= gameInfo.holdingsSize) p = 0;
10482              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10483                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10484              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10485                         board[BOARD_HEIGHT-1-p][1] = 0;
10486         }
10487       }
10488       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10489           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10490         /* [HGM] holdings: Add to holdings, if holdings exist */
10491         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10492                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10493                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10494         }
10495         p = (int) captured;
10496         if (p >= (int) BlackPawn) {
10497           p -= (int)BlackPawn;
10498           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10499                   /* Restore shogi-promoted piece to its original  first */
10500                   captured = (ChessSquare) (DEMOTED(captured));
10501                   p = DEMOTED(p);
10502           }
10503           p = PieceToNumber((ChessSquare)p);
10504           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10505           board[p][BOARD_WIDTH-2]++;
10506           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10507         } else {
10508           p -= (int)WhitePawn;
10509           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10510                   captured = (ChessSquare) (DEMOTED(captured));
10511                   p = DEMOTED(p);
10512           }
10513           p = PieceToNumber((ChessSquare)p);
10514           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10515           board[BOARD_HEIGHT-1-p][1]++;
10516           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10517         }
10518       }
10519     } else if (gameInfo.variant == VariantAtomic) {
10520       if (captured != EmptySquare) {
10521         int y, x;
10522         for (y = toY-1; y <= toY+1; y++) {
10523           for (x = toX-1; x <= toX+1; x++) {
10524             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10525                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10526               board[y][x] = EmptySquare;
10527             }
10528           }
10529         }
10530         board[toY][toX] = EmptySquare;
10531       }
10532     }
10533
10534     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10535         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10536     } else
10537     if(promoChar == '+') {
10538         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10539         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10540         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10541           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10542     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10543         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10544         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10545            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10546         board[toY][toX] = newPiece;
10547     }
10548     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10549                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10550         // [HGM] superchess: take promotion piece out of holdings
10551         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10552         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10553             if(!--board[k][BOARD_WIDTH-2])
10554                 board[k][BOARD_WIDTH-1] = EmptySquare;
10555         } else {
10556             if(!--board[BOARD_HEIGHT-1-k][1])
10557                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10558         }
10559     }
10560 }
10561
10562 /* Updates forwardMostMove */
10563 void
10564 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10565 {
10566     int x = toX, y = toY;
10567     char *s = parseList[forwardMostMove];
10568     ChessSquare p = boards[forwardMostMove][toY][toX];
10569 //    forwardMostMove++; // [HGM] bare: moved downstream
10570
10571     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10572     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10573     (void) CoordsToAlgebraic(boards[forwardMostMove],
10574                              PosFlags(forwardMostMove),
10575                              fromY, fromX, y, x, (killX < 0)*promoChar,
10576                              s);
10577     if(kill2X >= 0 && kill2Y >= 0)
10578         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10579     if(killX >= 0 && killY >= 0)
10580         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10581                                            toX + AAA, toY + ONE - '0', promoChar);
10582
10583     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10584         int timeLeft; static int lastLoadFlag=0; int king, piece;
10585         piece = boards[forwardMostMove][fromY][fromX];
10586         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10587         if(gameInfo.variant == VariantKnightmate)
10588             king += (int) WhiteUnicorn - (int) WhiteKing;
10589         if(forwardMostMove == 0) {
10590             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10591                 fprintf(serverMoves, "%s;", UserName());
10592             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10593                 fprintf(serverMoves, "%s;", second.tidy);
10594             fprintf(serverMoves, "%s;", first.tidy);
10595             if(gameMode == MachinePlaysWhite)
10596                 fprintf(serverMoves, "%s;", UserName());
10597             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10598                 fprintf(serverMoves, "%s;", second.tidy);
10599         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10600         lastLoadFlag = loadFlag;
10601         // print base move
10602         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10603         // print castling suffix
10604         if( toY == fromY && piece == king ) {
10605             if(toX-fromX > 1)
10606                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10607             if(fromX-toX >1)
10608                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10609         }
10610         // e.p. suffix
10611         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10612              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10613              boards[forwardMostMove][toY][toX] == EmptySquare
10614              && fromX != toX && fromY != toY)
10615                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10616         // promotion suffix
10617         if(promoChar != NULLCHAR) {
10618             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10619                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10620                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10621             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10622         }
10623         if(!loadFlag) {
10624                 char buf[MOVE_LEN*2], *p; int len;
10625             fprintf(serverMoves, "/%d/%d",
10626                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10627             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10628             else                      timeLeft = blackTimeRemaining/1000;
10629             fprintf(serverMoves, "/%d", timeLeft);
10630                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10631                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10632                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10633                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10634             fprintf(serverMoves, "/%s", buf);
10635         }
10636         fflush(serverMoves);
10637     }
10638
10639     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10640         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10641       return;
10642     }
10643     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10644     if (commentList[forwardMostMove+1] != NULL) {
10645         free(commentList[forwardMostMove+1]);
10646         commentList[forwardMostMove+1] = NULL;
10647     }
10648     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10649     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10650     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10651     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10652     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10653     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10654     adjustedClock = FALSE;
10655     gameInfo.result = GameUnfinished;
10656     if (gameInfo.resultDetails != NULL) {
10657         free(gameInfo.resultDetails);
10658         gameInfo.resultDetails = NULL;
10659     }
10660     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10661                               moveList[forwardMostMove - 1]);
10662     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10663       case MT_NONE:
10664       case MT_STALEMATE:
10665       default:
10666         break;
10667       case MT_CHECK:
10668         if(!IS_SHOGI(gameInfo.variant))
10669             strcat(parseList[forwardMostMove - 1], "+");
10670         break;
10671       case MT_CHECKMATE:
10672       case MT_STAINMATE:
10673         strcat(parseList[forwardMostMove - 1], "#");
10674         break;
10675     }
10676 }
10677
10678 /* Updates currentMove if not pausing */
10679 void
10680 ShowMove (int fromX, int fromY, int toX, int toY)
10681 {
10682     int instant = (gameMode == PlayFromGameFile) ?
10683         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10684     if(appData.noGUI) return;
10685     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10686         if (!instant) {
10687             if (forwardMostMove == currentMove + 1) {
10688                 AnimateMove(boards[forwardMostMove - 1],
10689                             fromX, fromY, toX, toY);
10690             }
10691         }
10692         currentMove = forwardMostMove;
10693     }
10694
10695     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10696
10697     if (instant) return;
10698
10699     DisplayMove(currentMove - 1);
10700     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10701             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10702                 SetHighlights(fromX, fromY, toX, toY);
10703             }
10704     }
10705     DrawPosition(FALSE, boards[currentMove]);
10706     DisplayBothClocks();
10707     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10708 }
10709
10710 void
10711 SendEgtPath (ChessProgramState *cps)
10712 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10713         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10714
10715         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10716
10717         while(*p) {
10718             char c, *q = name+1, *r, *s;
10719
10720             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10721             while(*p && *p != ',') *q++ = *p++;
10722             *q++ = ':'; *q = 0;
10723             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10724                 strcmp(name, ",nalimov:") == 0 ) {
10725                 // take nalimov path from the menu-changeable option first, if it is defined
10726               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10727                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10728             } else
10729             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10730                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10731                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10732                 s = r = StrStr(s, ":") + 1; // beginning of path info
10733                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10734                 c = *r; *r = 0;             // temporarily null-terminate path info
10735                     *--q = 0;               // strip of trailig ':' from name
10736                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10737                 *r = c;
10738                 SendToProgram(buf,cps);     // send egtbpath command for this format
10739             }
10740             if(*p == ',') p++; // read away comma to position for next format name
10741         }
10742 }
10743
10744 static int
10745 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10746 {
10747       int width = 8, height = 8, holdings = 0;             // most common sizes
10748       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10749       // correct the deviations default for each variant
10750       if( v == VariantXiangqi ) width = 9,  height = 10;
10751       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10752       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10753       if( v == VariantCapablanca || v == VariantCapaRandom ||
10754           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10755                                 width = 10;
10756       if( v == VariantCourier ) width = 12;
10757       if( v == VariantSuper )                            holdings = 8;
10758       if( v == VariantGreat )   width = 10,              holdings = 8;
10759       if( v == VariantSChess )                           holdings = 7;
10760       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10761       if( v == VariantChuChess) width = 10, height = 10;
10762       if( v == VariantChu )     width = 12, height = 12;
10763       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10764              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10765              holdingsSize >= 0 && holdingsSize != holdings;
10766 }
10767
10768 char variantError[MSG_SIZ];
10769
10770 char *
10771 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10772 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10773       char *p, *variant = VariantName(v);
10774       static char b[MSG_SIZ];
10775       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10776            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10777                                                holdingsSize, variant); // cook up sized variant name
10778            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10779            if(StrStr(list, b) == NULL) {
10780                // specific sized variant not known, check if general sizing allowed
10781                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10782                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10783                             boardWidth, boardHeight, holdingsSize, engine);
10784                    return NULL;
10785                }
10786                /* [HGM] here we really should compare with the maximum supported board size */
10787            }
10788       } else snprintf(b, MSG_SIZ,"%s", variant);
10789       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10790       p = StrStr(list, b);
10791       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10792       if(p == NULL) {
10793           // occurs not at all in list, or only as sub-string
10794           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10795           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10796               int l = strlen(variantError);
10797               char *q;
10798               while(p != list && p[-1] != ',') p--;
10799               q = strchr(p, ',');
10800               if(q) *q = NULLCHAR;
10801               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10802               if(q) *q= ',';
10803           }
10804           return NULL;
10805       }
10806       return b;
10807 }
10808
10809 void
10810 InitChessProgram (ChessProgramState *cps, int setup)
10811 /* setup needed to setup FRC opening position */
10812 {
10813     char buf[MSG_SIZ], *b;
10814     if (appData.noChessProgram) return;
10815     hintRequested = FALSE;
10816     bookRequested = FALSE;
10817
10818     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10819     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10820     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10821     if(cps->memSize) { /* [HGM] memory */
10822       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10823         SendToProgram(buf, cps);
10824     }
10825     SendEgtPath(cps); /* [HGM] EGT */
10826     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10827       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10828         SendToProgram(buf, cps);
10829     }
10830
10831     setboardSpoiledMachineBlack = FALSE;
10832     SendToProgram(cps->initString, cps);
10833     if (gameInfo.variant != VariantNormal &&
10834         gameInfo.variant != VariantLoadable
10835         /* [HGM] also send variant if board size non-standard */
10836         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10837
10838       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10839                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10840
10841       if (b == NULL) {
10842         VariantClass v;
10843         char c, *q = cps->variants, *p = strchr(q, ',');
10844         if(p) *p = NULLCHAR;
10845         v = StringToVariant(q);
10846         DisplayError(variantError, 0);
10847         if(v != VariantUnknown && cps == &first) {
10848             int w, h, s;
10849             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10850                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10851             ASSIGN(appData.variant, q);
10852             Reset(TRUE, FALSE);
10853         }
10854         if(p) *p = ',';
10855         return;
10856       }
10857
10858       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10859       SendToProgram(buf, cps);
10860     }
10861     currentlyInitializedVariant = gameInfo.variant;
10862
10863     /* [HGM] send opening position in FRC to first engine */
10864     if(setup) {
10865           SendToProgram("force\n", cps);
10866           SendBoard(cps, 0);
10867           /* engine is now in force mode! Set flag to wake it up after first move. */
10868           setboardSpoiledMachineBlack = 1;
10869     }
10870
10871     if (cps->sendICS) {
10872       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10873       SendToProgram(buf, cps);
10874     }
10875     cps->maybeThinking = FALSE;
10876     cps->offeredDraw = 0;
10877     if (!appData.icsActive) {
10878         SendTimeControl(cps, movesPerSession, timeControl,
10879                         timeIncrement, appData.searchDepth,
10880                         searchTime);
10881     }
10882     if (appData.showThinking
10883         // [HGM] thinking: four options require thinking output to be sent
10884         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10885                                 ) {
10886         SendToProgram("post\n", cps);
10887     }
10888     SendToProgram("hard\n", cps);
10889     if (!appData.ponderNextMove) {
10890         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10891            it without being sure what state we are in first.  "hard"
10892            is not a toggle, so that one is OK.
10893          */
10894         SendToProgram("easy\n", cps);
10895     }
10896     if (cps->usePing) {
10897       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10898       SendToProgram(buf, cps);
10899     }
10900     cps->initDone = TRUE;
10901     ClearEngineOutputPane(cps == &second);
10902 }
10903
10904
10905 void
10906 ResendOptions (ChessProgramState *cps)
10907 { // send the stored value of the options
10908   int i;
10909   char buf[MSG_SIZ];
10910   Option *opt = cps->option;
10911   for(i=0; i<cps->nrOptions; i++, opt++) {
10912       switch(opt->type) {
10913         case Spin:
10914         case Slider:
10915         case CheckBox:
10916             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10917           break;
10918         case ComboBox:
10919           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10920           break;
10921         default:
10922             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10923           break;
10924         case Button:
10925         case SaveButton:
10926           continue;
10927       }
10928       SendToProgram(buf, cps);
10929   }
10930 }
10931
10932 void
10933 StartChessProgram (ChessProgramState *cps)
10934 {
10935     char buf[MSG_SIZ];
10936     int err;
10937
10938     if (appData.noChessProgram) return;
10939     cps->initDone = FALSE;
10940
10941     if (strcmp(cps->host, "localhost") == 0) {
10942         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10943     } else if (*appData.remoteShell == NULLCHAR) {
10944         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10945     } else {
10946         if (*appData.remoteUser == NULLCHAR) {
10947           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10948                     cps->program);
10949         } else {
10950           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10951                     cps->host, appData.remoteUser, cps->program);
10952         }
10953         err = StartChildProcess(buf, "", &cps->pr);
10954     }
10955
10956     if (err != 0) {
10957       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10958         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10959         if(cps != &first) return;
10960         appData.noChessProgram = TRUE;
10961         ThawUI();
10962         SetNCPMode();
10963 //      DisplayFatalError(buf, err, 1);
10964 //      cps->pr = NoProc;
10965 //      cps->isr = NULL;
10966         return;
10967     }
10968
10969     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10970     if (cps->protocolVersion > 1) {
10971       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10972       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10973         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10974         cps->comboCnt = 0;  //                and values of combo boxes
10975       }
10976       SendToProgram(buf, cps);
10977       if(cps->reload) ResendOptions(cps);
10978     } else {
10979       SendToProgram("xboard\n", cps);
10980     }
10981 }
10982
10983 void
10984 TwoMachinesEventIfReady P((void))
10985 {
10986   static int curMess = 0;
10987   if (first.lastPing != first.lastPong) {
10988     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10989     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10990     return;
10991   }
10992   if (second.lastPing != second.lastPong) {
10993     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10994     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10995     return;
10996   }
10997   DisplayMessage("", ""); curMess = 0;
10998   TwoMachinesEvent();
10999 }
11000
11001 char *
11002 MakeName (char *template)
11003 {
11004     time_t clock;
11005     struct tm *tm;
11006     static char buf[MSG_SIZ];
11007     char *p = buf;
11008     int i;
11009
11010     clock = time((time_t *)NULL);
11011     tm = localtime(&clock);
11012
11013     while(*p++ = *template++) if(p[-1] == '%') {
11014         switch(*template++) {
11015           case 0:   *p = 0; return buf;
11016           case 'Y': i = tm->tm_year+1900; break;
11017           case 'y': i = tm->tm_year-100; break;
11018           case 'M': i = tm->tm_mon+1; break;
11019           case 'd': i = tm->tm_mday; break;
11020           case 'h': i = tm->tm_hour; break;
11021           case 'm': i = tm->tm_min; break;
11022           case 's': i = tm->tm_sec; break;
11023           default:  i = 0;
11024         }
11025         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11026     }
11027     return buf;
11028 }
11029
11030 int
11031 CountPlayers (char *p)
11032 {
11033     int n = 0;
11034     while(p = strchr(p, '\n')) p++, n++; // count participants
11035     return n;
11036 }
11037
11038 FILE *
11039 WriteTourneyFile (char *results, FILE *f)
11040 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11041     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11042     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11043         // create a file with tournament description
11044         fprintf(f, "-participants {%s}\n", appData.participants);
11045         fprintf(f, "-seedBase %d\n", appData.seedBase);
11046         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11047         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11048         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11049         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11050         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11051         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11052         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11053         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11054         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11055         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11056         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11057         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11058         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11059         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11060         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11061         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11062         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11063         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11064         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11065         fprintf(f, "-smpCores %d\n", appData.smpCores);
11066         if(searchTime > 0)
11067                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11068         else {
11069                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11070                 fprintf(f, "-tc %s\n", appData.timeControl);
11071                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11072         }
11073         fprintf(f, "-results \"%s\"\n", results);
11074     }
11075     return f;
11076 }
11077
11078 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11079
11080 void
11081 Substitute (char *participants, int expunge)
11082 {
11083     int i, changed, changes=0, nPlayers=0;
11084     char *p, *q, *r, buf[MSG_SIZ];
11085     if(participants == NULL) return;
11086     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11087     r = p = participants; q = appData.participants;
11088     while(*p && *p == *q) {
11089         if(*p == '\n') r = p+1, nPlayers++;
11090         p++; q++;
11091     }
11092     if(*p) { // difference
11093         while(*p && *p++ != '\n')
11094                                  ;
11095         while(*q && *q++ != '\n')
11096                                  ;
11097       changed = nPlayers;
11098         changes = 1 + (strcmp(p, q) != 0);
11099     }
11100     if(changes == 1) { // a single engine mnemonic was changed
11101         q = r; while(*q) nPlayers += (*q++ == '\n');
11102         p = buf; while(*r && (*p = *r++) != '\n') p++;
11103         *p = NULLCHAR;
11104         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11105         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11106         if(mnemonic[i]) { // The substitute is valid
11107             FILE *f;
11108             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11109                 flock(fileno(f), LOCK_EX);
11110                 ParseArgsFromFile(f);
11111                 fseek(f, 0, SEEK_SET);
11112                 FREE(appData.participants); appData.participants = participants;
11113                 if(expunge) { // erase results of replaced engine
11114                     int len = strlen(appData.results), w, b, dummy;
11115                     for(i=0; i<len; i++) {
11116                         Pairing(i, nPlayers, &w, &b, &dummy);
11117                         if((w == changed || b == changed) && appData.results[i] == '*') {
11118                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11119                             fclose(f);
11120                             return;
11121                         }
11122                     }
11123                     for(i=0; i<len; i++) {
11124                         Pairing(i, nPlayers, &w, &b, &dummy);
11125                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11126                     }
11127                 }
11128                 WriteTourneyFile(appData.results, f);
11129                 fclose(f); // release lock
11130                 return;
11131             }
11132         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11133     }
11134     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11135     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11136     free(participants);
11137     return;
11138 }
11139
11140 int
11141 CheckPlayers (char *participants)
11142 {
11143         int i;
11144         char buf[MSG_SIZ], *p;
11145         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11146         while(p = strchr(participants, '\n')) {
11147             *p = NULLCHAR;
11148             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11149             if(!mnemonic[i]) {
11150                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11151                 *p = '\n';
11152                 DisplayError(buf, 0);
11153                 return 1;
11154             }
11155             *p = '\n';
11156             participants = p + 1;
11157         }
11158         return 0;
11159 }
11160
11161 int
11162 CreateTourney (char *name)
11163 {
11164         FILE *f;
11165         if(matchMode && strcmp(name, appData.tourneyFile)) {
11166              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11167         }
11168         if(name[0] == NULLCHAR) {
11169             if(appData.participants[0])
11170                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11171             return 0;
11172         }
11173         f = fopen(name, "r");
11174         if(f) { // file exists
11175             ASSIGN(appData.tourneyFile, name);
11176             ParseArgsFromFile(f); // parse it
11177         } else {
11178             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11179             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11180                 DisplayError(_("Not enough participants"), 0);
11181                 return 0;
11182             }
11183             if(CheckPlayers(appData.participants)) return 0;
11184             ASSIGN(appData.tourneyFile, name);
11185             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11186             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11187         }
11188         fclose(f);
11189         appData.noChessProgram = FALSE;
11190         appData.clockMode = TRUE;
11191         SetGNUMode();
11192         return 1;
11193 }
11194
11195 int
11196 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11197 {
11198     char buf[MSG_SIZ], *p, *q;
11199     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11200     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11201     skip = !all && group[0]; // if group requested, we start in skip mode
11202     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11203         p = names; q = buf; header = 0;
11204         while(*p && *p != '\n') *q++ = *p++;
11205         *q = 0;
11206         if(*p == '\n') p++;
11207         if(buf[0] == '#') {
11208             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11209             depth++; // we must be entering a new group
11210             if(all) continue; // suppress printing group headers when complete list requested
11211             header = 1;
11212             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11213         }
11214         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11215         if(engineList[i]) free(engineList[i]);
11216         engineList[i] = strdup(buf);
11217         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11218         if(engineMnemonic[i]) free(engineMnemonic[i]);
11219         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11220             strcat(buf, " (");
11221             sscanf(q + 8, "%s", buf + strlen(buf));
11222             strcat(buf, ")");
11223         }
11224         engineMnemonic[i] = strdup(buf);
11225         i++;
11226     }
11227     engineList[i] = engineMnemonic[i] = NULL;
11228     return i;
11229 }
11230
11231 // following implemented as macro to avoid type limitations
11232 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11233
11234 void
11235 SwapEngines (int n)
11236 {   // swap settings for first engine and other engine (so far only some selected options)
11237     int h;
11238     char *p;
11239     if(n == 0) return;
11240     SWAP(directory, p)
11241     SWAP(chessProgram, p)
11242     SWAP(isUCI, h)
11243     SWAP(hasOwnBookUCI, h)
11244     SWAP(protocolVersion, h)
11245     SWAP(reuse, h)
11246     SWAP(scoreIsAbsolute, h)
11247     SWAP(timeOdds, h)
11248     SWAP(logo, p)
11249     SWAP(pgnName, p)
11250     SWAP(pvSAN, h)
11251     SWAP(engOptions, p)
11252     SWAP(engInitString, p)
11253     SWAP(computerString, p)
11254     SWAP(features, p)
11255     SWAP(fenOverride, p)
11256     SWAP(NPS, h)
11257     SWAP(accumulateTC, h)
11258     SWAP(drawDepth, h)
11259     SWAP(host, p)
11260     SWAP(pseudo, h)
11261 }
11262
11263 int
11264 GetEngineLine (char *s, int n)
11265 {
11266     int i;
11267     char buf[MSG_SIZ];
11268     extern char *icsNames;
11269     if(!s || !*s) return 0;
11270     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11271     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11272     if(!mnemonic[i]) return 0;
11273     if(n == 11) return 1; // just testing if there was a match
11274     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11275     if(n == 1) SwapEngines(n);
11276     ParseArgsFromString(buf);
11277     if(n == 1) SwapEngines(n);
11278     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11279         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11280         ParseArgsFromString(buf);
11281     }
11282     return 1;
11283 }
11284
11285 int
11286 SetPlayer (int player, char *p)
11287 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11288     int i;
11289     char buf[MSG_SIZ], *engineName;
11290     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11291     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11292     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11293     if(mnemonic[i]) {
11294         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11295         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11296         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11297         ParseArgsFromString(buf);
11298     } else { // no engine with this nickname is installed!
11299         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11300         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11301         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11302         ModeHighlight();
11303         DisplayError(buf, 0);
11304         return 0;
11305     }
11306     free(engineName);
11307     return i;
11308 }
11309
11310 char *recentEngines;
11311
11312 void
11313 RecentEngineEvent (int nr)
11314 {
11315     int n;
11316 //    SwapEngines(1); // bump first to second
11317 //    ReplaceEngine(&second, 1); // and load it there
11318     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11319     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11320     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11321         ReplaceEngine(&first, 0);
11322         FloatToFront(&appData.recentEngineList, command[n]);
11323     }
11324 }
11325
11326 int
11327 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11328 {   // determine players from game number
11329     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11330
11331     if(appData.tourneyType == 0) {
11332         roundsPerCycle = (nPlayers - 1) | 1;
11333         pairingsPerRound = nPlayers / 2;
11334     } else if(appData.tourneyType > 0) {
11335         roundsPerCycle = nPlayers - appData.tourneyType;
11336         pairingsPerRound = appData.tourneyType;
11337     }
11338     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11339     gamesPerCycle = gamesPerRound * roundsPerCycle;
11340     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11341     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11342     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11343     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11344     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11345     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11346
11347     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11348     if(appData.roundSync) *syncInterval = gamesPerRound;
11349
11350     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11351
11352     if(appData.tourneyType == 0) {
11353         if(curPairing == (nPlayers-1)/2 ) {
11354             *whitePlayer = curRound;
11355             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11356         } else {
11357             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11358             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11359             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11360             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11361         }
11362     } else if(appData.tourneyType > 1) {
11363         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11364         *whitePlayer = curRound + appData.tourneyType;
11365     } else if(appData.tourneyType > 0) {
11366         *whitePlayer = curPairing;
11367         *blackPlayer = curRound + appData.tourneyType;
11368     }
11369
11370     // take care of white/black alternation per round.
11371     // For cycles and games this is already taken care of by default, derived from matchGame!
11372     return curRound & 1;
11373 }
11374
11375 int
11376 NextTourneyGame (int nr, int *swapColors)
11377 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11378     char *p, *q;
11379     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11380     FILE *tf;
11381     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11382     tf = fopen(appData.tourneyFile, "r");
11383     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11384     ParseArgsFromFile(tf); fclose(tf);
11385     InitTimeControls(); // TC might be altered from tourney file
11386
11387     nPlayers = CountPlayers(appData.participants); // count participants
11388     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11389     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11390
11391     if(syncInterval) {
11392         p = q = appData.results;
11393         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11394         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11395             DisplayMessage(_("Waiting for other game(s)"),"");
11396             waitingForGame = TRUE;
11397             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11398             return 0;
11399         }
11400         waitingForGame = FALSE;
11401     }
11402
11403     if(appData.tourneyType < 0) {
11404         if(nr>=0 && !pairingReceived) {
11405             char buf[1<<16];
11406             if(pairing.pr == NoProc) {
11407                 if(!appData.pairingEngine[0]) {
11408                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11409                     return 0;
11410                 }
11411                 StartChessProgram(&pairing); // starts the pairing engine
11412             }
11413             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11414             SendToProgram(buf, &pairing);
11415             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11416             SendToProgram(buf, &pairing);
11417             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11418         }
11419         pairingReceived = 0;                              // ... so we continue here
11420         *swapColors = 0;
11421         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11422         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11423         matchGame = 1; roundNr = nr / syncInterval + 1;
11424     }
11425
11426     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11427
11428     // redefine engines, engine dir, etc.
11429     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11430     if(first.pr == NoProc) {
11431       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11432       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11433     }
11434     if(second.pr == NoProc) {
11435       SwapEngines(1);
11436       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11437       SwapEngines(1);         // and make that valid for second engine by swapping
11438       InitEngine(&second, 1);
11439     }
11440     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11441     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11442     return OK;
11443 }
11444
11445 void
11446 NextMatchGame ()
11447 {   // performs game initialization that does not invoke engines, and then tries to start the game
11448     int res, firstWhite, swapColors = 0;
11449     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11450     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
11451         char buf[MSG_SIZ];
11452         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11453         if(strcmp(buf, currentDebugFile)) { // name has changed
11454             FILE *f = fopen(buf, "w");
11455             if(f) { // if opening the new file failed, just keep using the old one
11456                 ASSIGN(currentDebugFile, buf);
11457                 fclose(debugFP);
11458                 debugFP = f;
11459             }
11460             if(appData.serverFileName) {
11461                 if(serverFP) fclose(serverFP);
11462                 serverFP = fopen(appData.serverFileName, "w");
11463                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11464                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11465             }
11466         }
11467     }
11468     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11469     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11470     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11471     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11472     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11473     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11474     Reset(FALSE, first.pr != NoProc);
11475     res = LoadGameOrPosition(matchGame); // setup game
11476     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11477     if(!res) return; // abort when bad game/pos file
11478     if(appData.epd) {// in EPD mode we make sure first engine is to move
11479         firstWhite = !(forwardMostMove & 1);
11480         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11481         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11482     }
11483     TwoMachinesEvent();
11484 }
11485
11486 void
11487 UserAdjudicationEvent (int result)
11488 {
11489     ChessMove gameResult = GameIsDrawn;
11490
11491     if( result > 0 ) {
11492         gameResult = WhiteWins;
11493     }
11494     else if( result < 0 ) {
11495         gameResult = BlackWins;
11496     }
11497
11498     if( gameMode == TwoMachinesPlay ) {
11499         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11500     }
11501 }
11502
11503
11504 // [HGM] save: calculate checksum of game to make games easily identifiable
11505 int
11506 StringCheckSum (char *s)
11507 {
11508         int i = 0;
11509         if(s==NULL) return 0;
11510         while(*s) i = i*259 + *s++;
11511         return i;
11512 }
11513
11514 int
11515 GameCheckSum ()
11516 {
11517         int i, sum=0;
11518         for(i=backwardMostMove; i<forwardMostMove; i++) {
11519                 sum += pvInfoList[i].depth;
11520                 sum += StringCheckSum(parseList[i]);
11521                 sum += StringCheckSum(commentList[i]);
11522                 sum *= 261;
11523         }
11524         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11525         return sum + StringCheckSum(commentList[i]);
11526 } // end of save patch
11527
11528 void
11529 GameEnds (ChessMove result, char *resultDetails, int whosays)
11530 {
11531     GameMode nextGameMode;
11532     int isIcsGame;
11533     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11534
11535     if(endingGame) return; /* [HGM] crash: forbid recursion */
11536     endingGame = 1;
11537     if(twoBoards) { // [HGM] dual: switch back to one board
11538         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11539         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11540     }
11541     if (appData.debugMode) {
11542       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11543               result, resultDetails ? resultDetails : "(null)", whosays);
11544     }
11545
11546     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11547
11548     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11549
11550     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11551         /* If we are playing on ICS, the server decides when the
11552            game is over, but the engine can offer to draw, claim
11553            a draw, or resign.
11554          */
11555 #if ZIPPY
11556         if (appData.zippyPlay && first.initDone) {
11557             if (result == GameIsDrawn) {
11558                 /* In case draw still needs to be claimed */
11559                 SendToICS(ics_prefix);
11560                 SendToICS("draw\n");
11561             } else if (StrCaseStr(resultDetails, "resign")) {
11562                 SendToICS(ics_prefix);
11563                 SendToICS("resign\n");
11564             }
11565         }
11566 #endif
11567         endingGame = 0; /* [HGM] crash */
11568         return;
11569     }
11570
11571     /* If we're loading the game from a file, stop */
11572     if (whosays == GE_FILE) {
11573       (void) StopLoadGameTimer();
11574       gameFileFP = NULL;
11575     }
11576
11577     /* Cancel draw offers */
11578     first.offeredDraw = second.offeredDraw = 0;
11579
11580     /* If this is an ICS game, only ICS can really say it's done;
11581        if not, anyone can. */
11582     isIcsGame = (gameMode == IcsPlayingWhite ||
11583                  gameMode == IcsPlayingBlack ||
11584                  gameMode == IcsObserving    ||
11585                  gameMode == IcsExamining);
11586
11587     if (!isIcsGame || whosays == GE_ICS) {
11588         /* OK -- not an ICS game, or ICS said it was done */
11589         StopClocks();
11590         if (!isIcsGame && !appData.noChessProgram)
11591           SetUserThinkingEnables();
11592
11593         /* [HGM] if a machine claims the game end we verify this claim */
11594         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11595             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11596                 char claimer;
11597                 ChessMove trueResult = (ChessMove) -1;
11598
11599                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11600                                             first.twoMachinesColor[0] :
11601                                             second.twoMachinesColor[0] ;
11602
11603                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11604                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11605                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11606                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11607                 } else
11608                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11609                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11610                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11611                 } else
11612                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11613                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11614                 }
11615
11616                 // now verify win claims, but not in drop games, as we don't understand those yet
11617                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11618                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11619                     (result == WhiteWins && claimer == 'w' ||
11620                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11621                       if (appData.debugMode) {
11622                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11623                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11624                       }
11625                       if(result != trueResult) {
11626                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11627                               result = claimer == 'w' ? BlackWins : WhiteWins;
11628                               resultDetails = buf;
11629                       }
11630                 } else
11631                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11632                     && (forwardMostMove <= backwardMostMove ||
11633                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11634                         (claimer=='b')==(forwardMostMove&1))
11635                                                                                   ) {
11636                       /* [HGM] verify: draws that were not flagged are false claims */
11637                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11638                       result = claimer == 'w' ? BlackWins : WhiteWins;
11639                       resultDetails = buf;
11640                 }
11641                 /* (Claiming a loss is accepted no questions asked!) */
11642             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11643                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11644                 result = GameUnfinished;
11645                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11646             }
11647             /* [HGM] bare: don't allow bare King to win */
11648             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11649                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11650                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11651                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11652                && result != GameIsDrawn)
11653             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11654                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11655                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11656                         if(p >= 0 && p <= (int)WhiteKing) k++;
11657                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11658                 }
11659                 if (appData.debugMode) {
11660                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11661                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11662                 }
11663                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11664                         result = GameIsDrawn;
11665                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11666                         resultDetails = buf;
11667                 }
11668             }
11669         }
11670
11671
11672         if(serverMoves != NULL && !loadFlag) { char c = '=';
11673             if(result==WhiteWins) c = '+';
11674             if(result==BlackWins) c = '-';
11675             if(resultDetails != NULL)
11676                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11677         }
11678         if (resultDetails != NULL) {
11679             gameInfo.result = result;
11680             gameInfo.resultDetails = StrSave(resultDetails);
11681
11682             /* display last move only if game was not loaded from file */
11683             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11684                 DisplayMove(currentMove - 1);
11685
11686             if (forwardMostMove != 0) {
11687                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11688                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11689                                                                 ) {
11690                     if (*appData.saveGameFile != NULLCHAR) {
11691                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11692                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11693                         else
11694                         SaveGameToFile(appData.saveGameFile, TRUE);
11695                     } else if (appData.autoSaveGames) {
11696                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11697                     }
11698                     if (*appData.savePositionFile != NULLCHAR) {
11699                         SavePositionToFile(appData.savePositionFile);
11700                     }
11701                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11702                 }
11703             }
11704
11705             /* Tell program how game ended in case it is learning */
11706             /* [HGM] Moved this to after saving the PGN, just in case */
11707             /* engine died and we got here through time loss. In that */
11708             /* case we will get a fatal error writing the pipe, which */
11709             /* would otherwise lose us the PGN.                       */
11710             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11711             /* output during GameEnds should never be fatal anymore   */
11712             if (gameMode == MachinePlaysWhite ||
11713                 gameMode == MachinePlaysBlack ||
11714                 gameMode == TwoMachinesPlay ||
11715                 gameMode == IcsPlayingWhite ||
11716                 gameMode == IcsPlayingBlack ||
11717                 gameMode == BeginningOfGame) {
11718                 char buf[MSG_SIZ];
11719                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11720                         resultDetails);
11721                 if (first.pr != NoProc) {
11722                     SendToProgram(buf, &first);
11723                 }
11724                 if (second.pr != NoProc &&
11725                     gameMode == TwoMachinesPlay) {
11726                     SendToProgram(buf, &second);
11727                 }
11728             }
11729         }
11730
11731         if (appData.icsActive) {
11732             if (appData.quietPlay &&
11733                 (gameMode == IcsPlayingWhite ||
11734                  gameMode == IcsPlayingBlack)) {
11735                 SendToICS(ics_prefix);
11736                 SendToICS("set shout 1\n");
11737             }
11738             nextGameMode = IcsIdle;
11739             ics_user_moved = FALSE;
11740             /* clean up premove.  It's ugly when the game has ended and the
11741              * premove highlights are still on the board.
11742              */
11743             if (gotPremove) {
11744               gotPremove = FALSE;
11745               ClearPremoveHighlights();
11746               DrawPosition(FALSE, boards[currentMove]);
11747             }
11748             if (whosays == GE_ICS) {
11749                 switch (result) {
11750                 case WhiteWins:
11751                     if (gameMode == IcsPlayingWhite)
11752                         PlayIcsWinSound();
11753                     else if(gameMode == IcsPlayingBlack)
11754                         PlayIcsLossSound();
11755                     break;
11756                 case BlackWins:
11757                     if (gameMode == IcsPlayingBlack)
11758                         PlayIcsWinSound();
11759                     else if(gameMode == IcsPlayingWhite)
11760                         PlayIcsLossSound();
11761                     break;
11762                 case GameIsDrawn:
11763                     PlayIcsDrawSound();
11764                     break;
11765                 default:
11766                     PlayIcsUnfinishedSound();
11767                 }
11768             }
11769             if(appData.quitNext) { ExitEvent(0); return; }
11770         } else if (gameMode == EditGame ||
11771                    gameMode == PlayFromGameFile ||
11772                    gameMode == AnalyzeMode ||
11773                    gameMode == AnalyzeFile) {
11774             nextGameMode = gameMode;
11775         } else {
11776             nextGameMode = EndOfGame;
11777         }
11778         pausing = FALSE;
11779         ModeHighlight();
11780     } else {
11781         nextGameMode = gameMode;
11782     }
11783
11784     if (appData.noChessProgram) {
11785         gameMode = nextGameMode;
11786         ModeHighlight();
11787         endingGame = 0; /* [HGM] crash */
11788         return;
11789     }
11790
11791     if (first.reuse) {
11792         /* Put first chess program into idle state */
11793         if (first.pr != NoProc &&
11794             (gameMode == MachinePlaysWhite ||
11795              gameMode == MachinePlaysBlack ||
11796              gameMode == TwoMachinesPlay ||
11797              gameMode == IcsPlayingWhite ||
11798              gameMode == IcsPlayingBlack ||
11799              gameMode == BeginningOfGame)) {
11800             SendToProgram("force\n", &first);
11801             if (first.usePing) {
11802               char buf[MSG_SIZ];
11803               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11804               SendToProgram(buf, &first);
11805             }
11806         }
11807     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11808         /* Kill off first chess program */
11809         if (first.isr != NULL)
11810           RemoveInputSource(first.isr);
11811         first.isr = NULL;
11812
11813         if (first.pr != NoProc) {
11814             ExitAnalyzeMode();
11815             DoSleep( appData.delayBeforeQuit );
11816             SendToProgram("quit\n", &first);
11817             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11818             first.reload = TRUE;
11819         }
11820         first.pr = NoProc;
11821     }
11822     if (second.reuse) {
11823         /* Put second chess program into idle state */
11824         if (second.pr != NoProc &&
11825             gameMode == TwoMachinesPlay) {
11826             SendToProgram("force\n", &second);
11827             if (second.usePing) {
11828               char buf[MSG_SIZ];
11829               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11830               SendToProgram(buf, &second);
11831             }
11832         }
11833     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11834         /* Kill off second chess program */
11835         if (second.isr != NULL)
11836           RemoveInputSource(second.isr);
11837         second.isr = NULL;
11838
11839         if (second.pr != NoProc) {
11840             DoSleep( appData.delayBeforeQuit );
11841             SendToProgram("quit\n", &second);
11842             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11843             second.reload = TRUE;
11844         }
11845         second.pr = NoProc;
11846     }
11847
11848     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11849         char resChar = '=';
11850         switch (result) {
11851         case WhiteWins:
11852           resChar = '+';
11853           if (first.twoMachinesColor[0] == 'w') {
11854             first.matchWins++;
11855           } else {
11856             second.matchWins++;
11857           }
11858           break;
11859         case BlackWins:
11860           resChar = '-';
11861           if (first.twoMachinesColor[0] == 'b') {
11862             first.matchWins++;
11863           } else {
11864             second.matchWins++;
11865           }
11866           break;
11867         case GameUnfinished:
11868           resChar = ' ';
11869         default:
11870           break;
11871         }
11872
11873         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11874         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11875             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11876             ReserveGame(nextGame, resChar); // sets nextGame
11877             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11878             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11879         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11880
11881         if (nextGame <= appData.matchGames && !abortMatch) {
11882             gameMode = nextGameMode;
11883             matchGame = nextGame; // this will be overruled in tourney mode!
11884             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11885             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11886             endingGame = 0; /* [HGM] crash */
11887             return;
11888         } else {
11889             gameMode = nextGameMode;
11890             if(appData.epd) {
11891                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11892                 OutputKibitz(2, buf);
11893                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11894                 OutputKibitz(2, buf);
11895                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11896                 if(second.matchWins) OutputKibitz(2, buf);
11897                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11898                 OutputKibitz(2, buf);
11899             }
11900             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11901                      first.tidy, second.tidy,
11902                      first.matchWins, second.matchWins,
11903                      appData.matchGames - (first.matchWins + second.matchWins));
11904             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11905             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11906             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11907             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11908                 first.twoMachinesColor = "black\n";
11909                 second.twoMachinesColor = "white\n";
11910             } else {
11911                 first.twoMachinesColor = "white\n";
11912                 second.twoMachinesColor = "black\n";
11913             }
11914         }
11915     }
11916     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11917         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11918       ExitAnalyzeMode();
11919     gameMode = nextGameMode;
11920     ModeHighlight();
11921     endingGame = 0;  /* [HGM] crash */
11922     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11923         if(matchMode == TRUE) { // match through command line: exit with or without popup
11924             if(ranking) {
11925                 ToNrEvent(forwardMostMove);
11926                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11927                 else ExitEvent(0);
11928             } else DisplayFatalError(buf, 0, 0);
11929         } else { // match through menu; just stop, with or without popup
11930             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11931             ModeHighlight();
11932             if(ranking){
11933                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11934             } else DisplayNote(buf);
11935       }
11936       if(ranking) free(ranking);
11937     }
11938 }
11939
11940 /* Assumes program was just initialized (initString sent).
11941    Leaves program in force mode. */
11942 void
11943 FeedMovesToProgram (ChessProgramState *cps, int upto)
11944 {
11945     int i;
11946
11947     if (appData.debugMode)
11948       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11949               startedFromSetupPosition ? "position and " : "",
11950               backwardMostMove, upto, cps->which);
11951     if(currentlyInitializedVariant != gameInfo.variant) {
11952       char buf[MSG_SIZ];
11953         // [HGM] variantswitch: make engine aware of new variant
11954         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11955                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11956                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11957         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11958         SendToProgram(buf, cps);
11959         currentlyInitializedVariant = gameInfo.variant;
11960     }
11961     SendToProgram("force\n", cps);
11962     if (startedFromSetupPosition) {
11963         SendBoard(cps, backwardMostMove);
11964     if (appData.debugMode) {
11965         fprintf(debugFP, "feedMoves\n");
11966     }
11967     }
11968     for (i = backwardMostMove; i < upto; i++) {
11969         SendMoveToProgram(i, cps);
11970     }
11971 }
11972
11973
11974 int
11975 ResurrectChessProgram ()
11976 {
11977      /* The chess program may have exited.
11978         If so, restart it and feed it all the moves made so far. */
11979     static int doInit = 0;
11980
11981     if (appData.noChessProgram) return 1;
11982
11983     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11984         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11985         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11986         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11987     } else {
11988         if (first.pr != NoProc) return 1;
11989         StartChessProgram(&first);
11990     }
11991     InitChessProgram(&first, FALSE);
11992     FeedMovesToProgram(&first, currentMove);
11993
11994     if (!first.sendTime) {
11995         /* can't tell gnuchess what its clock should read,
11996            so we bow to its notion. */
11997         ResetClocks();
11998         timeRemaining[0][currentMove] = whiteTimeRemaining;
11999         timeRemaining[1][currentMove] = blackTimeRemaining;
12000     }
12001
12002     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12003                 appData.icsEngineAnalyze) && first.analysisSupport) {
12004       SendToProgram("analyze\n", &first);
12005       first.analyzing = TRUE;
12006     }
12007     return 1;
12008 }
12009
12010 /*
12011  * Button procedures
12012  */
12013 void
12014 Reset (int redraw, int init)
12015 {
12016     int i;
12017
12018     if (appData.debugMode) {
12019         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12020                 redraw, init, gameMode);
12021     }
12022     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12023     deadRanks = 0; // assume entire board is used
12024     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12025     CleanupTail(); // [HGM] vari: delete any stored variations
12026     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12027     pausing = pauseExamInvalid = FALSE;
12028     startedFromSetupPosition = blackPlaysFirst = FALSE;
12029     firstMove = TRUE;
12030     whiteFlag = blackFlag = FALSE;
12031     userOfferedDraw = FALSE;
12032     hintRequested = bookRequested = FALSE;
12033     first.maybeThinking = FALSE;
12034     second.maybeThinking = FALSE;
12035     first.bookSuspend = FALSE; // [HGM] book
12036     second.bookSuspend = FALSE;
12037     thinkOutput[0] = NULLCHAR;
12038     lastHint[0] = NULLCHAR;
12039     ClearGameInfo(&gameInfo);
12040     gameInfo.variant = StringToVariant(appData.variant);
12041     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12042         gameInfo.variant = VariantUnknown;
12043         strncpy(engineVariant, appData.variant, MSG_SIZ);
12044     }
12045     ics_user_moved = ics_clock_paused = FALSE;
12046     ics_getting_history = H_FALSE;
12047     ics_gamenum = -1;
12048     white_holding[0] = black_holding[0] = NULLCHAR;
12049     ClearProgramStats();
12050     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12051
12052     ResetFrontEnd();
12053     ClearHighlights();
12054     flipView = appData.flipView;
12055     ClearPremoveHighlights();
12056     gotPremove = FALSE;
12057     alarmSounded = FALSE;
12058     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12059
12060     GameEnds(EndOfFile, NULL, GE_PLAYER);
12061     if(appData.serverMovesName != NULL) {
12062         /* [HGM] prepare to make moves file for broadcasting */
12063         clock_t t = clock();
12064         if(serverMoves != NULL) fclose(serverMoves);
12065         serverMoves = fopen(appData.serverMovesName, "r");
12066         if(serverMoves != NULL) {
12067             fclose(serverMoves);
12068             /* delay 15 sec before overwriting, so all clients can see end */
12069             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12070         }
12071         serverMoves = fopen(appData.serverMovesName, "w");
12072     }
12073
12074     ExitAnalyzeMode();
12075     gameMode = BeginningOfGame;
12076     ModeHighlight();
12077     if(appData.icsActive) gameInfo.variant = VariantNormal;
12078     currentMove = forwardMostMove = backwardMostMove = 0;
12079     MarkTargetSquares(1);
12080     InitPosition(redraw);
12081     for (i = 0; i < MAX_MOVES; i++) {
12082         if (commentList[i] != NULL) {
12083             free(commentList[i]);
12084             commentList[i] = NULL;
12085         }
12086     }
12087     ResetClocks();
12088     timeRemaining[0][0] = whiteTimeRemaining;
12089     timeRemaining[1][0] = blackTimeRemaining;
12090
12091     if (first.pr == NoProc) {
12092         StartChessProgram(&first);
12093     }
12094     if (init) {
12095             InitChessProgram(&first, startedFromSetupPosition);
12096     }
12097     DisplayTitle("");
12098     DisplayMessage("", "");
12099     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12100     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12101     ClearMap();        // [HGM] exclude: invalidate map
12102 }
12103
12104 void
12105 AutoPlayGameLoop ()
12106 {
12107     for (;;) {
12108         if (!AutoPlayOneMove())
12109           return;
12110         if (matchMode || appData.timeDelay == 0)
12111           continue;
12112         if (appData.timeDelay < 0)
12113           return;
12114         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12115         break;
12116     }
12117 }
12118
12119 void
12120 AnalyzeNextGame()
12121 {
12122     ReloadGame(1); // next game
12123 }
12124
12125 int
12126 AutoPlayOneMove ()
12127 {
12128     int fromX, fromY, toX, toY;
12129
12130     if (appData.debugMode) {
12131       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12132     }
12133
12134     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12135       return FALSE;
12136
12137     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12138       pvInfoList[currentMove].depth = programStats.depth;
12139       pvInfoList[currentMove].score = programStats.score;
12140       pvInfoList[currentMove].time  = 0;
12141       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12142       else { // append analysis of final position as comment
12143         char buf[MSG_SIZ];
12144         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12145         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12146       }
12147       programStats.depth = 0;
12148     }
12149
12150     if (currentMove >= forwardMostMove) {
12151       if(gameMode == AnalyzeFile) {
12152           if(appData.loadGameIndex == -1) {
12153             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12154           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12155           } else {
12156           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12157         }
12158       }
12159 //      gameMode = EndOfGame;
12160 //      ModeHighlight();
12161
12162       /* [AS] Clear current move marker at the end of a game */
12163       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12164
12165       return FALSE;
12166     }
12167
12168     toX = moveList[currentMove][2] - AAA;
12169     toY = moveList[currentMove][3] - ONE;
12170
12171     if (moveList[currentMove][1] == '@') {
12172         if (appData.highlightLastMove) {
12173             SetHighlights(-1, -1, toX, toY);
12174         }
12175     } else {
12176         fromX = moveList[currentMove][0] - AAA;
12177         fromY = moveList[currentMove][1] - ONE;
12178
12179         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12180
12181         if(moveList[currentMove][4] == ';') { // multi-leg
12182             killX = moveList[currentMove][5] - AAA;
12183             killY = moveList[currentMove][6] - ONE;
12184         }
12185         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12186         killX = killY = -1;
12187
12188         if (appData.highlightLastMove) {
12189             SetHighlights(fromX, fromY, toX, toY);
12190         }
12191     }
12192     DisplayMove(currentMove);
12193     SendMoveToProgram(currentMove++, &first);
12194     DisplayBothClocks();
12195     DrawPosition(FALSE, boards[currentMove]);
12196     // [HGM] PV info: always display, routine tests if empty
12197     DisplayComment(currentMove - 1, commentList[currentMove]);
12198     return TRUE;
12199 }
12200
12201
12202 int
12203 LoadGameOneMove (ChessMove readAhead)
12204 {
12205     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12206     char promoChar = NULLCHAR;
12207     ChessMove moveType;
12208     char move[MSG_SIZ];
12209     char *p, *q;
12210
12211     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12212         gameMode != AnalyzeMode && gameMode != Training) {
12213         gameFileFP = NULL;
12214         return FALSE;
12215     }
12216
12217     yyboardindex = forwardMostMove;
12218     if (readAhead != EndOfFile) {
12219       moveType = readAhead;
12220     } else {
12221       if (gameFileFP == NULL)
12222           return FALSE;
12223       moveType = (ChessMove) Myylex();
12224     }
12225
12226     done = FALSE;
12227     switch (moveType) {
12228       case Comment:
12229         if (appData.debugMode)
12230           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12231         p = yy_text;
12232
12233         /* append the comment but don't display it */
12234         AppendComment(currentMove, p, FALSE);
12235         return TRUE;
12236
12237       case WhiteCapturesEnPassant:
12238       case BlackCapturesEnPassant:
12239       case WhitePromotion:
12240       case BlackPromotion:
12241       case WhiteNonPromotion:
12242       case BlackNonPromotion:
12243       case NormalMove:
12244       case FirstLeg:
12245       case WhiteKingSideCastle:
12246       case WhiteQueenSideCastle:
12247       case BlackKingSideCastle:
12248       case BlackQueenSideCastle:
12249       case WhiteKingSideCastleWild:
12250       case WhiteQueenSideCastleWild:
12251       case BlackKingSideCastleWild:
12252       case BlackQueenSideCastleWild:
12253       /* PUSH Fabien */
12254       case WhiteHSideCastleFR:
12255       case WhiteASideCastleFR:
12256       case BlackHSideCastleFR:
12257       case BlackASideCastleFR:
12258       /* POP Fabien */
12259         if (appData.debugMode)
12260           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12261         fromX = currentMoveString[0] - AAA;
12262         fromY = currentMoveString[1] - ONE;
12263         toX = currentMoveString[2] - AAA;
12264         toY = currentMoveString[3] - ONE;
12265         promoChar = currentMoveString[4];
12266         if(promoChar == ';') promoChar = currentMoveString[7];
12267         break;
12268
12269       case WhiteDrop:
12270       case BlackDrop:
12271         if (appData.debugMode)
12272           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12273         fromX = moveType == WhiteDrop ?
12274           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12275         (int) CharToPiece(ToLower(currentMoveString[0]));
12276         fromY = DROP_RANK;
12277         toX = currentMoveString[2] - AAA;
12278         toY = currentMoveString[3] - ONE;
12279         break;
12280
12281       case WhiteWins:
12282       case BlackWins:
12283       case GameIsDrawn:
12284       case GameUnfinished:
12285         if (appData.debugMode)
12286           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12287         p = strchr(yy_text, '{');
12288         if (p == NULL) p = strchr(yy_text, '(');
12289         if (p == NULL) {
12290             p = yy_text;
12291             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12292         } else {
12293             q = strchr(p, *p == '{' ? '}' : ')');
12294             if (q != NULL) *q = NULLCHAR;
12295             p++;
12296         }
12297         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12298         GameEnds(moveType, p, GE_FILE);
12299         done = TRUE;
12300         if (cmailMsgLoaded) {
12301             ClearHighlights();
12302             flipView = WhiteOnMove(currentMove);
12303             if (moveType == GameUnfinished) flipView = !flipView;
12304             if (appData.debugMode)
12305               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12306         }
12307         break;
12308
12309       case EndOfFile:
12310         if (appData.debugMode)
12311           fprintf(debugFP, "Parser hit end of file\n");
12312         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12313           case MT_NONE:
12314           case MT_CHECK:
12315             break;
12316           case MT_CHECKMATE:
12317           case MT_STAINMATE:
12318             if (WhiteOnMove(currentMove)) {
12319                 GameEnds(BlackWins, "Black mates", GE_FILE);
12320             } else {
12321                 GameEnds(WhiteWins, "White mates", GE_FILE);
12322             }
12323             break;
12324           case MT_STALEMATE:
12325             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12326             break;
12327         }
12328         done = TRUE;
12329         break;
12330
12331       case MoveNumberOne:
12332         if (lastLoadGameStart == GNUChessGame) {
12333             /* GNUChessGames have numbers, but they aren't move numbers */
12334             if (appData.debugMode)
12335               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12336                       yy_text, (int) moveType);
12337             return LoadGameOneMove(EndOfFile); /* tail recursion */
12338         }
12339         /* else fall thru */
12340
12341       case XBoardGame:
12342       case GNUChessGame:
12343       case PGNTag:
12344         /* Reached start of next game in file */
12345         if (appData.debugMode)
12346           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12347         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12348           case MT_NONE:
12349           case MT_CHECK:
12350             break;
12351           case MT_CHECKMATE:
12352           case MT_STAINMATE:
12353             if (WhiteOnMove(currentMove)) {
12354                 GameEnds(BlackWins, "Black mates", GE_FILE);
12355             } else {
12356                 GameEnds(WhiteWins, "White mates", GE_FILE);
12357             }
12358             break;
12359           case MT_STALEMATE:
12360             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12361             break;
12362         }
12363         done = TRUE;
12364         break;
12365
12366       case PositionDiagram:     /* should not happen; ignore */
12367       case ElapsedTime:         /* ignore */
12368       case NAG:                 /* ignore */
12369         if (appData.debugMode)
12370           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12371                   yy_text, (int) moveType);
12372         return LoadGameOneMove(EndOfFile); /* tail recursion */
12373
12374       case IllegalMove:
12375         if (appData.testLegality) {
12376             if (appData.debugMode)
12377               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12378             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12379                     (forwardMostMove / 2) + 1,
12380                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12381             DisplayError(move, 0);
12382             done = TRUE;
12383         } else {
12384             if (appData.debugMode)
12385               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12386                       yy_text, currentMoveString);
12387             if(currentMoveString[1] == '@') {
12388                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12389                 fromY = DROP_RANK;
12390             } else {
12391                 fromX = currentMoveString[0] - AAA;
12392                 fromY = currentMoveString[1] - ONE;
12393             }
12394             toX = currentMoveString[2] - AAA;
12395             toY = currentMoveString[3] - ONE;
12396             promoChar = currentMoveString[4];
12397         }
12398         break;
12399
12400       case AmbiguousMove:
12401         if (appData.debugMode)
12402           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12403         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12404                 (forwardMostMove / 2) + 1,
12405                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12406         DisplayError(move, 0);
12407         done = TRUE;
12408         break;
12409
12410       default:
12411       case ImpossibleMove:
12412         if (appData.debugMode)
12413           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12414         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12415                 (forwardMostMove / 2) + 1,
12416                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12417         DisplayError(move, 0);
12418         done = TRUE;
12419         break;
12420     }
12421
12422     if (done) {
12423         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12424             DrawPosition(FALSE, boards[currentMove]);
12425             DisplayBothClocks();
12426             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12427               DisplayComment(currentMove - 1, commentList[currentMove]);
12428         }
12429         (void) StopLoadGameTimer();
12430         gameFileFP = NULL;
12431         cmailOldMove = forwardMostMove;
12432         return FALSE;
12433     } else {
12434         /* currentMoveString is set as a side-effect of yylex */
12435
12436         thinkOutput[0] = NULLCHAR;
12437         MakeMove(fromX, fromY, toX, toY, promoChar);
12438         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12439         currentMove = forwardMostMove;
12440         return TRUE;
12441     }
12442 }
12443
12444 /* Load the nth game from the given file */
12445 int
12446 LoadGameFromFile (char *filename, int n, char *title, int useList)
12447 {
12448     FILE *f;
12449     char buf[MSG_SIZ];
12450
12451     if (strcmp(filename, "-") == 0) {
12452         f = stdin;
12453         title = "stdin";
12454     } else {
12455         f = fopen(filename, "rb");
12456         if (f == NULL) {
12457           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12458             DisplayError(buf, errno);
12459             return FALSE;
12460         }
12461     }
12462     if (fseek(f, 0, 0) == -1) {
12463         /* f is not seekable; probably a pipe */
12464         useList = FALSE;
12465     }
12466     if (useList && n == 0) {
12467         int error = GameListBuild(f);
12468         if (error) {
12469             DisplayError(_("Cannot build game list"), error);
12470         } else if (!ListEmpty(&gameList) &&
12471                    ((ListGame *) gameList.tailPred)->number > 1) {
12472             GameListPopUp(f, title);
12473             return TRUE;
12474         }
12475         GameListDestroy();
12476         n = 1;
12477     }
12478     if (n == 0) n = 1;
12479     return LoadGame(f, n, title, FALSE);
12480 }
12481
12482
12483 void
12484 MakeRegisteredMove ()
12485 {
12486     int fromX, fromY, toX, toY;
12487     char promoChar;
12488     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12489         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12490           case CMAIL_MOVE:
12491           case CMAIL_DRAW:
12492             if (appData.debugMode)
12493               fprintf(debugFP, "Restoring %s for game %d\n",
12494                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12495
12496             thinkOutput[0] = NULLCHAR;
12497             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12498             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12499             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12500             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12501             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12502             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12503             MakeMove(fromX, fromY, toX, toY, promoChar);
12504             ShowMove(fromX, fromY, toX, toY);
12505
12506             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12507               case MT_NONE:
12508               case MT_CHECK:
12509                 break;
12510
12511               case MT_CHECKMATE:
12512               case MT_STAINMATE:
12513                 if (WhiteOnMove(currentMove)) {
12514                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12515                 } else {
12516                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12517                 }
12518                 break;
12519
12520               case MT_STALEMATE:
12521                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12522                 break;
12523             }
12524
12525             break;
12526
12527           case CMAIL_RESIGN:
12528             if (WhiteOnMove(currentMove)) {
12529                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12530             } else {
12531                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12532             }
12533             break;
12534
12535           case CMAIL_ACCEPT:
12536             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12537             break;
12538
12539           default:
12540             break;
12541         }
12542     }
12543
12544     return;
12545 }
12546
12547 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12548 int
12549 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12550 {
12551     int retVal;
12552
12553     if (gameNumber > nCmailGames) {
12554         DisplayError(_("No more games in this message"), 0);
12555         return FALSE;
12556     }
12557     if (f == lastLoadGameFP) {
12558         int offset = gameNumber - lastLoadGameNumber;
12559         if (offset == 0) {
12560             cmailMsg[0] = NULLCHAR;
12561             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12562                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12563                 nCmailMovesRegistered--;
12564             }
12565             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12566             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12567                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12568             }
12569         } else {
12570             if (! RegisterMove()) return FALSE;
12571         }
12572     }
12573
12574     retVal = LoadGame(f, gameNumber, title, useList);
12575
12576     /* Make move registered during previous look at this game, if any */
12577     MakeRegisteredMove();
12578
12579     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12580         commentList[currentMove]
12581           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12582         DisplayComment(currentMove - 1, commentList[currentMove]);
12583     }
12584
12585     return retVal;
12586 }
12587
12588 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12589 int
12590 ReloadGame (int offset)
12591 {
12592     int gameNumber = lastLoadGameNumber + offset;
12593     if (lastLoadGameFP == NULL) {
12594         DisplayError(_("No game has been loaded yet"), 0);
12595         return FALSE;
12596     }
12597     if (gameNumber <= 0) {
12598         DisplayError(_("Can't back up any further"), 0);
12599         return FALSE;
12600     }
12601     if (cmailMsgLoaded) {
12602         return CmailLoadGame(lastLoadGameFP, gameNumber,
12603                              lastLoadGameTitle, lastLoadGameUseList);
12604     } else {
12605         return LoadGame(lastLoadGameFP, gameNumber,
12606                         lastLoadGameTitle, lastLoadGameUseList);
12607     }
12608 }
12609
12610 int keys[EmptySquare+1];
12611
12612 int
12613 PositionMatches (Board b1, Board b2)
12614 {
12615     int r, f, sum=0;
12616     switch(appData.searchMode) {
12617         case 1: return CompareWithRights(b1, b2);
12618         case 2:
12619             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12620                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12621             }
12622             return TRUE;
12623         case 3:
12624             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12625               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12626                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12627             }
12628             return sum==0;
12629         case 4:
12630             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12631                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12632             }
12633             return sum==0;
12634     }
12635     return TRUE;
12636 }
12637
12638 #define Q_PROMO  4
12639 #define Q_EP     3
12640 #define Q_BCASTL 2
12641 #define Q_WCASTL 1
12642
12643 int pieceList[256], quickBoard[256];
12644 ChessSquare pieceType[256] = { EmptySquare };
12645 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12646 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12647 int soughtTotal, turn;
12648 Boolean epOK, flipSearch;
12649
12650 typedef struct {
12651     unsigned char piece, to;
12652 } Move;
12653
12654 #define DSIZE (250000)
12655
12656 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12657 Move *moveDatabase = initialSpace;
12658 unsigned int movePtr, dataSize = DSIZE;
12659
12660 int
12661 MakePieceList (Board board, int *counts)
12662 {
12663     int r, f, n=Q_PROMO, total=0;
12664     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12665     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12666         int sq = f + (r<<4);
12667         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12668             quickBoard[sq] = ++n;
12669             pieceList[n] = sq;
12670             pieceType[n] = board[r][f];
12671             counts[board[r][f]]++;
12672             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12673             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12674             total++;
12675         }
12676     }
12677     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12678     return total;
12679 }
12680
12681 void
12682 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12683 {
12684     int sq = fromX + (fromY<<4);
12685     int piece = quickBoard[sq], rook;
12686     quickBoard[sq] = 0;
12687     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12688     if(piece == pieceList[1] && fromY == toY) {
12689       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12690         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12691         moveDatabase[movePtr++].piece = Q_WCASTL;
12692         quickBoard[sq] = piece;
12693         piece = quickBoard[from]; quickBoard[from] = 0;
12694         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12695       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12696         quickBoard[sq] = 0; // remove Rook
12697         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12698         moveDatabase[movePtr++].piece = Q_WCASTL;
12699         quickBoard[sq] = pieceList[1]; // put King
12700         piece = rook;
12701         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12702       }
12703     } else
12704     if(piece == pieceList[2] && fromY == toY) {
12705       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12706         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12707         moveDatabase[movePtr++].piece = Q_BCASTL;
12708         quickBoard[sq] = piece;
12709         piece = quickBoard[from]; quickBoard[from] = 0;
12710         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12711       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12712         quickBoard[sq] = 0; // remove Rook
12713         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12714         moveDatabase[movePtr++].piece = Q_BCASTL;
12715         quickBoard[sq] = pieceList[2]; // put King
12716         piece = rook;
12717         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12718       }
12719     } else
12720     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12721         quickBoard[(fromY<<4)+toX] = 0;
12722         moveDatabase[movePtr].piece = Q_EP;
12723         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12724         moveDatabase[movePtr].to = sq;
12725     } else
12726     if(promoPiece != pieceType[piece]) {
12727         moveDatabase[movePtr++].piece = Q_PROMO;
12728         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12729     }
12730     moveDatabase[movePtr].piece = piece;
12731     quickBoard[sq] = piece;
12732     movePtr++;
12733 }
12734
12735 int
12736 PackGame (Board board)
12737 {
12738     Move *newSpace = NULL;
12739     moveDatabase[movePtr].piece = 0; // terminate previous game
12740     if(movePtr > dataSize) {
12741         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12742         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12743         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12744         if(newSpace) {
12745             int i;
12746             Move *p = moveDatabase, *q = newSpace;
12747             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12748             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12749             moveDatabase = newSpace;
12750         } else { // calloc failed, we must be out of memory. Too bad...
12751             dataSize = 0; // prevent calloc events for all subsequent games
12752             return 0;     // and signal this one isn't cached
12753         }
12754     }
12755     movePtr++;
12756     MakePieceList(board, counts);
12757     return movePtr;
12758 }
12759
12760 int
12761 QuickCompare (Board board, int *minCounts, int *maxCounts)
12762 {   // compare according to search mode
12763     int r, f;
12764     switch(appData.searchMode)
12765     {
12766       case 1: // exact position match
12767         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12768         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12769             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12770         }
12771         break;
12772       case 2: // can have extra material on empty squares
12773         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12774             if(board[r][f] == EmptySquare) continue;
12775             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12776         }
12777         break;
12778       case 3: // material with exact Pawn structure
12779         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12780             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12781             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12782         } // fall through to material comparison
12783       case 4: // exact material
12784         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12785         break;
12786       case 6: // material range with given imbalance
12787         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12788         // fall through to range comparison
12789       case 5: // material range
12790         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12791     }
12792     return TRUE;
12793 }
12794
12795 int
12796 QuickScan (Board board, Move *move)
12797 {   // reconstruct game,and compare all positions in it
12798     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12799     do {
12800         int piece = move->piece;
12801         int to = move->to, from = pieceList[piece];
12802         if(found < 0) { // if already found just scan to game end for final piece count
12803           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12804            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12805            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12806                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12807             ) {
12808             static int lastCounts[EmptySquare+1];
12809             int i;
12810             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12811             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12812           } else stretch = 0;
12813           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12814           if(found >= 0 && !appData.minPieces) return found;
12815         }
12816         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12817           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12818           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12819             piece = (++move)->piece;
12820             from = pieceList[piece];
12821             counts[pieceType[piece]]--;
12822             pieceType[piece] = (ChessSquare) move->to;
12823             counts[move->to]++;
12824           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12825             counts[pieceType[quickBoard[to]]]--;
12826             quickBoard[to] = 0; total--;
12827             move++;
12828             continue;
12829           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12830             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12831             from  = pieceList[piece]; // so this must be King
12832             quickBoard[from] = 0;
12833             pieceList[piece] = to;
12834             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12835             quickBoard[from] = 0; // rook
12836             quickBoard[to] = piece;
12837             to = move->to; piece = move->piece;
12838             goto aftercastle;
12839           }
12840         }
12841         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12842         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12843         quickBoard[from] = 0;
12844       aftercastle:
12845         quickBoard[to] = piece;
12846         pieceList[piece] = to;
12847         cnt++; turn ^= 3;
12848         move++;
12849     } while(1);
12850 }
12851
12852 void
12853 InitSearch ()
12854 {
12855     int r, f;
12856     flipSearch = FALSE;
12857     CopyBoard(soughtBoard, boards[currentMove]);
12858     soughtTotal = MakePieceList(soughtBoard, maxSought);
12859     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12860     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12861     CopyBoard(reverseBoard, boards[currentMove]);
12862     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12863         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12864         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12865         reverseBoard[r][f] = piece;
12866     }
12867     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12868     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12869     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12870                  || (boards[currentMove][CASTLING][2] == NoRights ||
12871                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12872                  && (boards[currentMove][CASTLING][5] == NoRights ||
12873                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12874       ) {
12875         flipSearch = TRUE;
12876         CopyBoard(flipBoard, soughtBoard);
12877         CopyBoard(rotateBoard, reverseBoard);
12878         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12879             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12880             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12881         }
12882     }
12883     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12884     if(appData.searchMode >= 5) {
12885         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12886         MakePieceList(soughtBoard, minSought);
12887         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12888     }
12889     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12890         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12891 }
12892
12893 GameInfo dummyInfo;
12894 static int creatingBook;
12895
12896 int
12897 GameContainsPosition (FILE *f, ListGame *lg)
12898 {
12899     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12900     int fromX, fromY, toX, toY;
12901     char promoChar;
12902     static int initDone=FALSE;
12903
12904     // weed out games based on numerical tag comparison
12905     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12906     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12907     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12908     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12909     if(!initDone) {
12910         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12911         initDone = TRUE;
12912     }
12913     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12914     else CopyBoard(boards[scratch], initialPosition); // default start position
12915     if(lg->moves) {
12916         turn = btm + 1;
12917         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12918         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12919     }
12920     if(btm) plyNr++;
12921     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12922     fseek(f, lg->offset, 0);
12923     yynewfile(f);
12924     while(1) {
12925         yyboardindex = scratch;
12926         quickFlag = plyNr+1;
12927         next = Myylex();
12928         quickFlag = 0;
12929         switch(next) {
12930             case PGNTag:
12931                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12932             default:
12933                 continue;
12934
12935             case XBoardGame:
12936             case GNUChessGame:
12937                 if(plyNr) return -1; // after we have seen moves, this is for new game
12938               continue;
12939
12940             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12941             case ImpossibleMove:
12942             case WhiteWins: // game ends here with these four
12943             case BlackWins:
12944             case GameIsDrawn:
12945             case GameUnfinished:
12946                 return -1;
12947
12948             case IllegalMove:
12949                 if(appData.testLegality) return -1;
12950             case WhiteCapturesEnPassant:
12951             case BlackCapturesEnPassant:
12952             case WhitePromotion:
12953             case BlackPromotion:
12954             case WhiteNonPromotion:
12955             case BlackNonPromotion:
12956             case NormalMove:
12957             case FirstLeg:
12958             case WhiteKingSideCastle:
12959             case WhiteQueenSideCastle:
12960             case BlackKingSideCastle:
12961             case BlackQueenSideCastle:
12962             case WhiteKingSideCastleWild:
12963             case WhiteQueenSideCastleWild:
12964             case BlackKingSideCastleWild:
12965             case BlackQueenSideCastleWild:
12966             case WhiteHSideCastleFR:
12967             case WhiteASideCastleFR:
12968             case BlackHSideCastleFR:
12969             case BlackASideCastleFR:
12970                 fromX = currentMoveString[0] - AAA;
12971                 fromY = currentMoveString[1] - ONE;
12972                 toX = currentMoveString[2] - AAA;
12973                 toY = currentMoveString[3] - ONE;
12974                 promoChar = currentMoveString[4];
12975                 break;
12976             case WhiteDrop:
12977             case BlackDrop:
12978                 fromX = next == WhiteDrop ?
12979                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12980                   (int) CharToPiece(ToLower(currentMoveString[0]));
12981                 fromY = DROP_RANK;
12982                 toX = currentMoveString[2] - AAA;
12983                 toY = currentMoveString[3] - ONE;
12984                 promoChar = 0;
12985                 break;
12986         }
12987         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12988         plyNr++;
12989         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12990         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12991         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12992         if(appData.findMirror) {
12993             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12994             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12995         }
12996     }
12997 }
12998
12999 /* Load the nth game from open file f */
13000 int
13001 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13002 {
13003     ChessMove cm;
13004     char buf[MSG_SIZ];
13005     int gn = gameNumber;
13006     ListGame *lg = NULL;
13007     int numPGNTags = 0, i;
13008     int err, pos = -1;
13009     GameMode oldGameMode;
13010     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13011     char oldName[MSG_SIZ];
13012
13013     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13014
13015     if (appData.debugMode)
13016         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13017
13018     if (gameMode == Training )
13019         SetTrainingModeOff();
13020
13021     oldGameMode = gameMode;
13022     if (gameMode != BeginningOfGame) {
13023       Reset(FALSE, TRUE);
13024     }
13025     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13026
13027     gameFileFP = f;
13028     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13029         fclose(lastLoadGameFP);
13030     }
13031
13032     if (useList) {
13033         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13034
13035         if (lg) {
13036             fseek(f, lg->offset, 0);
13037             GameListHighlight(gameNumber);
13038             pos = lg->position;
13039             gn = 1;
13040         }
13041         else {
13042             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13043               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13044             else
13045             DisplayError(_("Game number out of range"), 0);
13046             return FALSE;
13047         }
13048     } else {
13049         GameListDestroy();
13050         if (fseek(f, 0, 0) == -1) {
13051             if (f == lastLoadGameFP ?
13052                 gameNumber == lastLoadGameNumber + 1 :
13053                 gameNumber == 1) {
13054                 gn = 1;
13055             } else {
13056                 DisplayError(_("Can't seek on game file"), 0);
13057                 return FALSE;
13058             }
13059         }
13060     }
13061     lastLoadGameFP = f;
13062     lastLoadGameNumber = gameNumber;
13063     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13064     lastLoadGameUseList = useList;
13065
13066     yynewfile(f);
13067
13068     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13069       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13070                 lg->gameInfo.black);
13071             DisplayTitle(buf);
13072     } else if (*title != NULLCHAR) {
13073         if (gameNumber > 1) {
13074           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13075             DisplayTitle(buf);
13076         } else {
13077             DisplayTitle(title);
13078         }
13079     }
13080
13081     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13082         gameMode = PlayFromGameFile;
13083         ModeHighlight();
13084     }
13085
13086     currentMove = forwardMostMove = backwardMostMove = 0;
13087     CopyBoard(boards[0], initialPosition);
13088     StopClocks();
13089
13090     /*
13091      * Skip the first gn-1 games in the file.
13092      * Also skip over anything that precedes an identifiable
13093      * start of game marker, to avoid being confused by
13094      * garbage at the start of the file.  Currently
13095      * recognized start of game markers are the move number "1",
13096      * the pattern "gnuchess .* game", the pattern
13097      * "^[#;%] [^ ]* game file", and a PGN tag block.
13098      * A game that starts with one of the latter two patterns
13099      * will also have a move number 1, possibly
13100      * following a position diagram.
13101      * 5-4-02: Let's try being more lenient and allowing a game to
13102      * start with an unnumbered move.  Does that break anything?
13103      */
13104     cm = lastLoadGameStart = EndOfFile;
13105     while (gn > 0) {
13106         yyboardindex = forwardMostMove;
13107         cm = (ChessMove) Myylex();
13108         switch (cm) {
13109           case EndOfFile:
13110             if (cmailMsgLoaded) {
13111                 nCmailGames = CMAIL_MAX_GAMES - gn;
13112             } else {
13113                 Reset(TRUE, TRUE);
13114                 DisplayError(_("Game not found in file"), 0);
13115             }
13116             return FALSE;
13117
13118           case GNUChessGame:
13119           case XBoardGame:
13120             gn--;
13121             lastLoadGameStart = cm;
13122             break;
13123
13124           case MoveNumberOne:
13125             switch (lastLoadGameStart) {
13126               case GNUChessGame:
13127               case XBoardGame:
13128               case PGNTag:
13129                 break;
13130               case MoveNumberOne:
13131               case EndOfFile:
13132                 gn--;           /* count this game */
13133                 lastLoadGameStart = cm;
13134                 break;
13135               default:
13136                 /* impossible */
13137                 break;
13138             }
13139             break;
13140
13141           case PGNTag:
13142             switch (lastLoadGameStart) {
13143               case GNUChessGame:
13144               case PGNTag:
13145               case MoveNumberOne:
13146               case EndOfFile:
13147                 gn--;           /* count this game */
13148                 lastLoadGameStart = cm;
13149                 break;
13150               case XBoardGame:
13151                 lastLoadGameStart = cm; /* game counted already */
13152                 break;
13153               default:
13154                 /* impossible */
13155                 break;
13156             }
13157             if (gn > 0) {
13158                 do {
13159                     yyboardindex = forwardMostMove;
13160                     cm = (ChessMove) Myylex();
13161                 } while (cm == PGNTag || cm == Comment);
13162             }
13163             break;
13164
13165           case WhiteWins:
13166           case BlackWins:
13167           case GameIsDrawn:
13168             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13169                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13170                     != CMAIL_OLD_RESULT) {
13171                     nCmailResults ++ ;
13172                     cmailResult[  CMAIL_MAX_GAMES
13173                                 - gn - 1] = CMAIL_OLD_RESULT;
13174                 }
13175             }
13176             break;
13177
13178           case NormalMove:
13179           case FirstLeg:
13180             /* Only a NormalMove can be at the start of a game
13181              * without a position diagram. */
13182             if (lastLoadGameStart == EndOfFile ) {
13183               gn--;
13184               lastLoadGameStart = MoveNumberOne;
13185             }
13186             break;
13187
13188           default:
13189             break;
13190         }
13191     }
13192
13193     if (appData.debugMode)
13194       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13195
13196     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13197
13198     if (cm == XBoardGame) {
13199         /* Skip any header junk before position diagram and/or move 1 */
13200         for (;;) {
13201             yyboardindex = forwardMostMove;
13202             cm = (ChessMove) Myylex();
13203
13204             if (cm == EndOfFile ||
13205                 cm == GNUChessGame || cm == XBoardGame) {
13206                 /* Empty game; pretend end-of-file and handle later */
13207                 cm = EndOfFile;
13208                 break;
13209             }
13210
13211             if (cm == MoveNumberOne || cm == PositionDiagram ||
13212                 cm == PGNTag || cm == Comment)
13213               break;
13214         }
13215     } else if (cm == GNUChessGame) {
13216         if (gameInfo.event != NULL) {
13217             free(gameInfo.event);
13218         }
13219         gameInfo.event = StrSave(yy_text);
13220     }
13221
13222     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13223     while (cm == PGNTag) {
13224         if (appData.debugMode)
13225           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13226         err = ParsePGNTag(yy_text, &gameInfo);
13227         if (!err) numPGNTags++;
13228
13229         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13230         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13231             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13232             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13233             InitPosition(TRUE);
13234             oldVariant = gameInfo.variant;
13235             if (appData.debugMode)
13236               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13237         }
13238
13239
13240         if (gameInfo.fen != NULL) {
13241           Board initial_position;
13242           startedFromSetupPosition = TRUE;
13243           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13244             Reset(TRUE, TRUE);
13245             DisplayError(_("Bad FEN position in file"), 0);
13246             return FALSE;
13247           }
13248           CopyBoard(boards[0], initial_position);
13249           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13250             CopyBoard(initialPosition, initial_position);
13251           if (blackPlaysFirst) {
13252             currentMove = forwardMostMove = backwardMostMove = 1;
13253             CopyBoard(boards[1], initial_position);
13254             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13255             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13256             timeRemaining[0][1] = whiteTimeRemaining;
13257             timeRemaining[1][1] = blackTimeRemaining;
13258             if (commentList[0] != NULL) {
13259               commentList[1] = commentList[0];
13260               commentList[0] = NULL;
13261             }
13262           } else {
13263             currentMove = forwardMostMove = backwardMostMove = 0;
13264           }
13265           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13266           {   int i;
13267               initialRulePlies = FENrulePlies;
13268               for( i=0; i< nrCastlingRights; i++ )
13269                   initialRights[i] = initial_position[CASTLING][i];
13270           }
13271           yyboardindex = forwardMostMove;
13272           free(gameInfo.fen);
13273           gameInfo.fen = NULL;
13274         }
13275
13276         yyboardindex = forwardMostMove;
13277         cm = (ChessMove) Myylex();
13278
13279         /* Handle comments interspersed among the tags */
13280         while (cm == Comment) {
13281             char *p;
13282             if (appData.debugMode)
13283               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13284             p = yy_text;
13285             AppendComment(currentMove, p, FALSE);
13286             yyboardindex = forwardMostMove;
13287             cm = (ChessMove) Myylex();
13288         }
13289     }
13290
13291     /* don't rely on existence of Event tag since if game was
13292      * pasted from clipboard the Event tag may not exist
13293      */
13294     if (numPGNTags > 0){
13295         char *tags;
13296         if (gameInfo.variant == VariantNormal) {
13297           VariantClass v = StringToVariant(gameInfo.event);
13298           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13299           if(v < VariantShogi) gameInfo.variant = v;
13300         }
13301         if (!matchMode) {
13302           if( appData.autoDisplayTags ) {
13303             tags = PGNTags(&gameInfo);
13304             TagsPopUp(tags, CmailMsg());
13305             free(tags);
13306           }
13307         }
13308     } else {
13309         /* Make something up, but don't display it now */
13310         SetGameInfo();
13311         TagsPopDown();
13312     }
13313
13314     if (cm == PositionDiagram) {
13315         int i, j;
13316         char *p;
13317         Board initial_position;
13318
13319         if (appData.debugMode)
13320           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13321
13322         if (!startedFromSetupPosition) {
13323             p = yy_text;
13324             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13325               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13326                 switch (*p) {
13327                   case '{':
13328                   case '[':
13329                   case '-':
13330                   case ' ':
13331                   case '\t':
13332                   case '\n':
13333                   case '\r':
13334                     break;
13335                   default:
13336                     initial_position[i][j++] = CharToPiece(*p);
13337                     break;
13338                 }
13339             while (*p == ' ' || *p == '\t' ||
13340                    *p == '\n' || *p == '\r') p++;
13341
13342             if (strncmp(p, "black", strlen("black"))==0)
13343               blackPlaysFirst = TRUE;
13344             else
13345               blackPlaysFirst = FALSE;
13346             startedFromSetupPosition = TRUE;
13347
13348             CopyBoard(boards[0], initial_position);
13349             if (blackPlaysFirst) {
13350                 currentMove = forwardMostMove = backwardMostMove = 1;
13351                 CopyBoard(boards[1], initial_position);
13352                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13353                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13354                 timeRemaining[0][1] = whiteTimeRemaining;
13355                 timeRemaining[1][1] = blackTimeRemaining;
13356                 if (commentList[0] != NULL) {
13357                     commentList[1] = commentList[0];
13358                     commentList[0] = NULL;
13359                 }
13360             } else {
13361                 currentMove = forwardMostMove = backwardMostMove = 0;
13362             }
13363         }
13364         yyboardindex = forwardMostMove;
13365         cm = (ChessMove) Myylex();
13366     }
13367
13368   if(!creatingBook) {
13369     if (first.pr == NoProc) {
13370         StartChessProgram(&first);
13371     }
13372     InitChessProgram(&first, FALSE);
13373     if(gameInfo.variant == VariantUnknown && *oldName) {
13374         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13375         gameInfo.variant = v;
13376     }
13377     SendToProgram("force\n", &first);
13378     if (startedFromSetupPosition) {
13379         SendBoard(&first, forwardMostMove);
13380     if (appData.debugMode) {
13381         fprintf(debugFP, "Load Game\n");
13382     }
13383         DisplayBothClocks();
13384     }
13385   }
13386
13387     /* [HGM] server: flag to write setup moves in broadcast file as one */
13388     loadFlag = appData.suppressLoadMoves;
13389
13390     while (cm == Comment) {
13391         char *p;
13392         if (appData.debugMode)
13393           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13394         p = yy_text;
13395         AppendComment(currentMove, p, FALSE);
13396         yyboardindex = forwardMostMove;
13397         cm = (ChessMove) Myylex();
13398     }
13399
13400     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13401         cm == WhiteWins || cm == BlackWins ||
13402         cm == GameIsDrawn || cm == GameUnfinished) {
13403         DisplayMessage("", _("No moves in game"));
13404         if (cmailMsgLoaded) {
13405             if (appData.debugMode)
13406               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13407             ClearHighlights();
13408             flipView = FALSE;
13409         }
13410         DrawPosition(FALSE, boards[currentMove]);
13411         DisplayBothClocks();
13412         gameMode = EditGame;
13413         ModeHighlight();
13414         gameFileFP = NULL;
13415         cmailOldMove = 0;
13416         return TRUE;
13417     }
13418
13419     // [HGM] PV info: routine tests if comment empty
13420     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13421         DisplayComment(currentMove - 1, commentList[currentMove]);
13422     }
13423     if (!matchMode && appData.timeDelay != 0)
13424       DrawPosition(FALSE, boards[currentMove]);
13425
13426     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13427       programStats.ok_to_send = 1;
13428     }
13429
13430     /* if the first token after the PGN tags is a move
13431      * and not move number 1, retrieve it from the parser
13432      */
13433     if (cm != MoveNumberOne)
13434         LoadGameOneMove(cm);
13435
13436     /* load the remaining moves from the file */
13437     while (LoadGameOneMove(EndOfFile)) {
13438       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13439       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13440     }
13441
13442     /* rewind to the start of the game */
13443     currentMove = backwardMostMove;
13444
13445     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13446
13447     if (oldGameMode == AnalyzeFile) {
13448       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13449       AnalyzeFileEvent();
13450     } else
13451     if (oldGameMode == AnalyzeMode) {
13452       AnalyzeFileEvent();
13453     }
13454
13455     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13456         long int w, b; // [HGM] adjourn: restore saved clock times
13457         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13458         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13459             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13460             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13461         }
13462     }
13463
13464     if(creatingBook) return TRUE;
13465     if (!matchMode && pos > 0) {
13466         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13467     } else
13468     if (matchMode || appData.timeDelay == 0) {
13469       ToEndEvent();
13470     } else if (appData.timeDelay > 0) {
13471       AutoPlayGameLoop();
13472     }
13473
13474     if (appData.debugMode)
13475         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13476
13477     loadFlag = 0; /* [HGM] true game starts */
13478     return TRUE;
13479 }
13480
13481 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13482 int
13483 ReloadPosition (int offset)
13484 {
13485     int positionNumber = lastLoadPositionNumber + offset;
13486     if (lastLoadPositionFP == NULL) {
13487         DisplayError(_("No position has been loaded yet"), 0);
13488         return FALSE;
13489     }
13490     if (positionNumber <= 0) {
13491         DisplayError(_("Can't back up any further"), 0);
13492         return FALSE;
13493     }
13494     return LoadPosition(lastLoadPositionFP, positionNumber,
13495                         lastLoadPositionTitle);
13496 }
13497
13498 /* Load the nth position from the given file */
13499 int
13500 LoadPositionFromFile (char *filename, int n, char *title)
13501 {
13502     FILE *f;
13503     char buf[MSG_SIZ];
13504
13505     if (strcmp(filename, "-") == 0) {
13506         return LoadPosition(stdin, n, "stdin");
13507     } else {
13508         f = fopen(filename, "rb");
13509         if (f == NULL) {
13510             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13511             DisplayError(buf, errno);
13512             return FALSE;
13513         } else {
13514             return LoadPosition(f, n, title);
13515         }
13516     }
13517 }
13518
13519 /* Load the nth position from the given open file, and close it */
13520 int
13521 LoadPosition (FILE *f, int positionNumber, char *title)
13522 {
13523     char *p, line[MSG_SIZ];
13524     Board initial_position;
13525     int i, j, fenMode, pn;
13526
13527     if (gameMode == Training )
13528         SetTrainingModeOff();
13529
13530     if (gameMode != BeginningOfGame) {
13531         Reset(FALSE, TRUE);
13532     }
13533     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13534         fclose(lastLoadPositionFP);
13535     }
13536     if (positionNumber == 0) positionNumber = 1;
13537     lastLoadPositionFP = f;
13538     lastLoadPositionNumber = positionNumber;
13539     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13540     if (first.pr == NoProc && !appData.noChessProgram) {
13541       StartChessProgram(&first);
13542       InitChessProgram(&first, FALSE);
13543     }
13544     pn = positionNumber;
13545     if (positionNumber < 0) {
13546         /* Negative position number means to seek to that byte offset */
13547         if (fseek(f, -positionNumber, 0) == -1) {
13548             DisplayError(_("Can't seek on position file"), 0);
13549             return FALSE;
13550         };
13551         pn = 1;
13552     } else {
13553         if (fseek(f, 0, 0) == -1) {
13554             if (f == lastLoadPositionFP ?
13555                 positionNumber == lastLoadPositionNumber + 1 :
13556                 positionNumber == 1) {
13557                 pn = 1;
13558             } else {
13559                 DisplayError(_("Can't seek on position file"), 0);
13560                 return FALSE;
13561             }
13562         }
13563     }
13564     /* See if this file is FEN or old-style xboard */
13565     if (fgets(line, MSG_SIZ, f) == NULL) {
13566         DisplayError(_("Position not found in file"), 0);
13567         return FALSE;
13568     }
13569     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13570     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13571
13572     if (pn >= 2) {
13573         if (fenMode || line[0] == '#') pn--;
13574         while (pn > 0) {
13575             /* skip positions before number pn */
13576             if (fgets(line, MSG_SIZ, f) == NULL) {
13577                 Reset(TRUE, TRUE);
13578                 DisplayError(_("Position not found in file"), 0);
13579                 return FALSE;
13580             }
13581             if (fenMode || line[0] == '#') pn--;
13582         }
13583     }
13584
13585     if (fenMode) {
13586         char *p;
13587         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13588             DisplayError(_("Bad FEN position in file"), 0);
13589             return FALSE;
13590         }
13591         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13592             sscanf(p+4, "%[^;]", bestMove);
13593         } else *bestMove = NULLCHAR;
13594         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13595             sscanf(p+4, "%[^;]", avoidMove);
13596         } else *avoidMove = NULLCHAR;
13597     } else {
13598         (void) fgets(line, MSG_SIZ, f);
13599         (void) fgets(line, MSG_SIZ, f);
13600
13601         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13602             (void) fgets(line, MSG_SIZ, f);
13603             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13604                 if (*p == ' ')
13605                   continue;
13606                 initial_position[i][j++] = CharToPiece(*p);
13607             }
13608         }
13609
13610         blackPlaysFirst = FALSE;
13611         if (!feof(f)) {
13612             (void) fgets(line, MSG_SIZ, f);
13613             if (strncmp(line, "black", strlen("black"))==0)
13614               blackPlaysFirst = TRUE;
13615         }
13616     }
13617     startedFromSetupPosition = TRUE;
13618
13619     CopyBoard(boards[0], initial_position);
13620     if (blackPlaysFirst) {
13621         currentMove = forwardMostMove = backwardMostMove = 1;
13622         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13623         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13624         CopyBoard(boards[1], initial_position);
13625         DisplayMessage("", _("Black to play"));
13626     } else {
13627         currentMove = forwardMostMove = backwardMostMove = 0;
13628         DisplayMessage("", _("White to play"));
13629     }
13630     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13631     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13632         SendToProgram("force\n", &first);
13633         SendBoard(&first, forwardMostMove);
13634     }
13635     if (appData.debugMode) {
13636 int i, j;
13637   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13638   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13639         fprintf(debugFP, "Load Position\n");
13640     }
13641
13642     if (positionNumber > 1) {
13643       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13644         DisplayTitle(line);
13645     } else {
13646         DisplayTitle(title);
13647     }
13648     gameMode = EditGame;
13649     ModeHighlight();
13650     ResetClocks();
13651     timeRemaining[0][1] = whiteTimeRemaining;
13652     timeRemaining[1][1] = blackTimeRemaining;
13653     DrawPosition(FALSE, boards[currentMove]);
13654
13655     return TRUE;
13656 }
13657
13658
13659 void
13660 CopyPlayerNameIntoFileName (char **dest, char *src)
13661 {
13662     while (*src != NULLCHAR && *src != ',') {
13663         if (*src == ' ') {
13664             *(*dest)++ = '_';
13665             src++;
13666         } else {
13667             *(*dest)++ = *src++;
13668         }
13669     }
13670 }
13671
13672 char *
13673 DefaultFileName (char *ext)
13674 {
13675     static char def[MSG_SIZ];
13676     char *p;
13677
13678     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13679         p = def;
13680         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13681         *p++ = '-';
13682         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13683         *p++ = '.';
13684         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13685     } else {
13686         def[0] = NULLCHAR;
13687     }
13688     return def;
13689 }
13690
13691 /* Save the current game to the given file */
13692 int
13693 SaveGameToFile (char *filename, int append)
13694 {
13695     FILE *f;
13696     char buf[MSG_SIZ];
13697     int result, i, t,tot=0;
13698
13699     if (strcmp(filename, "-") == 0) {
13700         return SaveGame(stdout, 0, NULL);
13701     } else {
13702         for(i=0; i<10; i++) { // upto 10 tries
13703              f = fopen(filename, append ? "a" : "w");
13704              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13705              if(f || errno != 13) break;
13706              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13707              tot += t;
13708         }
13709         if (f == NULL) {
13710             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13711             DisplayError(buf, errno);
13712             return FALSE;
13713         } else {
13714             safeStrCpy(buf, lastMsg, MSG_SIZ);
13715             DisplayMessage(_("Waiting for access to save file"), "");
13716             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13717             DisplayMessage(_("Saving game"), "");
13718             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13719             result = SaveGame(f, 0, NULL);
13720             DisplayMessage(buf, "");
13721             return result;
13722         }
13723     }
13724 }
13725
13726 char *
13727 SavePart (char *str)
13728 {
13729     static char buf[MSG_SIZ];
13730     char *p;
13731
13732     p = strchr(str, ' ');
13733     if (p == NULL) return str;
13734     strncpy(buf, str, p - str);
13735     buf[p - str] = NULLCHAR;
13736     return buf;
13737 }
13738
13739 #define PGN_MAX_LINE 75
13740
13741 #define PGN_SIDE_WHITE  0
13742 #define PGN_SIDE_BLACK  1
13743
13744 static int
13745 FindFirstMoveOutOfBook (int side)
13746 {
13747     int result = -1;
13748
13749     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13750         int index = backwardMostMove;
13751         int has_book_hit = 0;
13752
13753         if( (index % 2) != side ) {
13754             index++;
13755         }
13756
13757         while( index < forwardMostMove ) {
13758             /* Check to see if engine is in book */
13759             int depth = pvInfoList[index].depth;
13760             int score = pvInfoList[index].score;
13761             int in_book = 0;
13762
13763             if( depth <= 2 ) {
13764                 in_book = 1;
13765             }
13766             else if( score == 0 && depth == 63 ) {
13767                 in_book = 1; /* Zappa */
13768             }
13769             else if( score == 2 && depth == 99 ) {
13770                 in_book = 1; /* Abrok */
13771             }
13772
13773             has_book_hit += in_book;
13774
13775             if( ! in_book ) {
13776                 result = index;
13777
13778                 break;
13779             }
13780
13781             index += 2;
13782         }
13783     }
13784
13785     return result;
13786 }
13787
13788 void
13789 GetOutOfBookInfo (char * buf)
13790 {
13791     int oob[2];
13792     int i;
13793     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13794
13795     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13796     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13797
13798     *buf = '\0';
13799
13800     if( oob[0] >= 0 || oob[1] >= 0 ) {
13801         for( i=0; i<2; i++ ) {
13802             int idx = oob[i];
13803
13804             if( idx >= 0 ) {
13805                 if( i > 0 && oob[0] >= 0 ) {
13806                     strcat( buf, "   " );
13807                 }
13808
13809                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13810                 sprintf( buf+strlen(buf), "%s%.2f",
13811                     pvInfoList[idx].score >= 0 ? "+" : "",
13812                     pvInfoList[idx].score / 100.0 );
13813             }
13814         }
13815     }
13816 }
13817
13818 /* Save game in PGN style */
13819 static void
13820 SaveGamePGN2 (FILE *f)
13821 {
13822     int i, offset, linelen, newblock;
13823 //    char *movetext;
13824     char numtext[32];
13825     int movelen, numlen, blank;
13826     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13827
13828     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13829
13830     PrintPGNTags(f, &gameInfo);
13831
13832     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13833
13834     if (backwardMostMove > 0 || startedFromSetupPosition) {
13835         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13836         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13837         fprintf(f, "\n{--------------\n");
13838         PrintPosition(f, backwardMostMove);
13839         fprintf(f, "--------------}\n");
13840         free(fen);
13841     }
13842     else {
13843         /* [AS] Out of book annotation */
13844         if( appData.saveOutOfBookInfo ) {
13845             char buf[64];
13846
13847             GetOutOfBookInfo( buf );
13848
13849             if( buf[0] != '\0' ) {
13850                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13851             }
13852         }
13853
13854         fprintf(f, "\n");
13855     }
13856
13857     i = backwardMostMove;
13858     linelen = 0;
13859     newblock = TRUE;
13860
13861     while (i < forwardMostMove) {
13862         /* Print comments preceding this move */
13863         if (commentList[i] != NULL) {
13864             if (linelen > 0) fprintf(f, "\n");
13865             fprintf(f, "%s", commentList[i]);
13866             linelen = 0;
13867             newblock = TRUE;
13868         }
13869
13870         /* Format move number */
13871         if ((i % 2) == 0)
13872           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13873         else
13874           if (newblock)
13875             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13876           else
13877             numtext[0] = NULLCHAR;
13878
13879         numlen = strlen(numtext);
13880         newblock = FALSE;
13881
13882         /* Print move number */
13883         blank = linelen > 0 && numlen > 0;
13884         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13885             fprintf(f, "\n");
13886             linelen = 0;
13887             blank = 0;
13888         }
13889         if (blank) {
13890             fprintf(f, " ");
13891             linelen++;
13892         }
13893         fprintf(f, "%s", numtext);
13894         linelen += numlen;
13895
13896         /* Get move */
13897         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13898         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13899
13900         /* Print move */
13901         blank = linelen > 0 && movelen > 0;
13902         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13903             fprintf(f, "\n");
13904             linelen = 0;
13905             blank = 0;
13906         }
13907         if (blank) {
13908             fprintf(f, " ");
13909             linelen++;
13910         }
13911         fprintf(f, "%s", move_buffer);
13912         linelen += movelen;
13913
13914         /* [AS] Add PV info if present */
13915         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13916             /* [HGM] add time */
13917             char buf[MSG_SIZ]; int seconds;
13918
13919             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13920
13921             if( seconds <= 0)
13922               buf[0] = 0;
13923             else
13924               if( seconds < 30 )
13925                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13926               else
13927                 {
13928                   seconds = (seconds + 4)/10; // round to full seconds
13929                   if( seconds < 60 )
13930                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13931                   else
13932                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13933                 }
13934
13935             if(appData.cumulativeTimePGN) {
13936                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13937             }
13938
13939             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13940                       pvInfoList[i].score >= 0 ? "+" : "",
13941                       pvInfoList[i].score / 100.0,
13942                       pvInfoList[i].depth,
13943                       buf );
13944
13945             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13946
13947             /* Print score/depth */
13948             blank = linelen > 0 && movelen > 0;
13949             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13950                 fprintf(f, "\n");
13951                 linelen = 0;
13952                 blank = 0;
13953             }
13954             if (blank) {
13955                 fprintf(f, " ");
13956                 linelen++;
13957             }
13958             fprintf(f, "%s", move_buffer);
13959             linelen += movelen;
13960         }
13961
13962         i++;
13963     }
13964
13965     /* Start a new line */
13966     if (linelen > 0) fprintf(f, "\n");
13967
13968     /* Print comments after last move */
13969     if (commentList[i] != NULL) {
13970         fprintf(f, "%s\n", commentList[i]);
13971     }
13972
13973     /* Print result */
13974     if (gameInfo.resultDetails != NULL &&
13975         gameInfo.resultDetails[0] != NULLCHAR) {
13976         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13977         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13978            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13979             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13980         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13981     } else {
13982         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13983     }
13984 }
13985
13986 /* Save game in PGN style and close the file */
13987 int
13988 SaveGamePGN (FILE *f)
13989 {
13990     SaveGamePGN2(f);
13991     fclose(f);
13992     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13993     return TRUE;
13994 }
13995
13996 /* Save game in old style and close the file */
13997 int
13998 SaveGameOldStyle (FILE *f)
13999 {
14000     int i, offset;
14001     time_t tm;
14002
14003     tm = time((time_t *) NULL);
14004
14005     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14006     PrintOpponents(f);
14007
14008     if (backwardMostMove > 0 || startedFromSetupPosition) {
14009         fprintf(f, "\n[--------------\n");
14010         PrintPosition(f, backwardMostMove);
14011         fprintf(f, "--------------]\n");
14012     } else {
14013         fprintf(f, "\n");
14014     }
14015
14016     i = backwardMostMove;
14017     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14018
14019     while (i < forwardMostMove) {
14020         if (commentList[i] != NULL) {
14021             fprintf(f, "[%s]\n", commentList[i]);
14022         }
14023
14024         if ((i % 2) == 1) {
14025             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14026             i++;
14027         } else {
14028             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14029             i++;
14030             if (commentList[i] != NULL) {
14031                 fprintf(f, "\n");
14032                 continue;
14033             }
14034             if (i >= forwardMostMove) {
14035                 fprintf(f, "\n");
14036                 break;
14037             }
14038             fprintf(f, "%s\n", parseList[i]);
14039             i++;
14040         }
14041     }
14042
14043     if (commentList[i] != NULL) {
14044         fprintf(f, "[%s]\n", commentList[i]);
14045     }
14046
14047     /* This isn't really the old style, but it's close enough */
14048     if (gameInfo.resultDetails != NULL &&
14049         gameInfo.resultDetails[0] != NULLCHAR) {
14050         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14051                 gameInfo.resultDetails);
14052     } else {
14053         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14054     }
14055
14056     fclose(f);
14057     return TRUE;
14058 }
14059
14060 /* Save the current game to open file f and close the file */
14061 int
14062 SaveGame (FILE *f, int dummy, char *dummy2)
14063 {
14064     if (gameMode == EditPosition) EditPositionDone(TRUE);
14065     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14066     if (appData.oldSaveStyle)
14067       return SaveGameOldStyle(f);
14068     else
14069       return SaveGamePGN(f);
14070 }
14071
14072 /* Save the current position to the given file */
14073 int
14074 SavePositionToFile (char *filename)
14075 {
14076     FILE *f;
14077     char buf[MSG_SIZ];
14078
14079     if (strcmp(filename, "-") == 0) {
14080         return SavePosition(stdout, 0, NULL);
14081     } else {
14082         f = fopen(filename, "a");
14083         if (f == NULL) {
14084             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14085             DisplayError(buf, errno);
14086             return FALSE;
14087         } else {
14088             safeStrCpy(buf, lastMsg, MSG_SIZ);
14089             DisplayMessage(_("Waiting for access to save file"), "");
14090             flock(fileno(f), LOCK_EX); // [HGM] lock
14091             DisplayMessage(_("Saving position"), "");
14092             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14093             SavePosition(f, 0, NULL);
14094             DisplayMessage(buf, "");
14095             return TRUE;
14096         }
14097     }
14098 }
14099
14100 /* Save the current position to the given open file and close the file */
14101 int
14102 SavePosition (FILE *f, int dummy, char *dummy2)
14103 {
14104     time_t tm;
14105     char *fen;
14106
14107     if (gameMode == EditPosition) EditPositionDone(TRUE);
14108     if (appData.oldSaveStyle) {
14109         tm = time((time_t *) NULL);
14110
14111         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14112         PrintOpponents(f);
14113         fprintf(f, "[--------------\n");
14114         PrintPosition(f, currentMove);
14115         fprintf(f, "--------------]\n");
14116     } else {
14117         fen = PositionToFEN(currentMove, NULL, 1);
14118         fprintf(f, "%s\n", fen);
14119         free(fen);
14120     }
14121     fclose(f);
14122     return TRUE;
14123 }
14124
14125 void
14126 ReloadCmailMsgEvent (int unregister)
14127 {
14128 #if !WIN32
14129     static char *inFilename = NULL;
14130     static char *outFilename;
14131     int i;
14132     struct stat inbuf, outbuf;
14133     int status;
14134
14135     /* Any registered moves are unregistered if unregister is set, */
14136     /* i.e. invoked by the signal handler */
14137     if (unregister) {
14138         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14139             cmailMoveRegistered[i] = FALSE;
14140             if (cmailCommentList[i] != NULL) {
14141                 free(cmailCommentList[i]);
14142                 cmailCommentList[i] = NULL;
14143             }
14144         }
14145         nCmailMovesRegistered = 0;
14146     }
14147
14148     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14149         cmailResult[i] = CMAIL_NOT_RESULT;
14150     }
14151     nCmailResults = 0;
14152
14153     if (inFilename == NULL) {
14154         /* Because the filenames are static they only get malloced once  */
14155         /* and they never get freed                                      */
14156         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14157         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14158
14159         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14160         sprintf(outFilename, "%s.out", appData.cmailGameName);
14161     }
14162
14163     status = stat(outFilename, &outbuf);
14164     if (status < 0) {
14165         cmailMailedMove = FALSE;
14166     } else {
14167         status = stat(inFilename, &inbuf);
14168         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14169     }
14170
14171     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14172        counts the games, notes how each one terminated, etc.
14173
14174        It would be nice to remove this kludge and instead gather all
14175        the information while building the game list.  (And to keep it
14176        in the game list nodes instead of having a bunch of fixed-size
14177        parallel arrays.)  Note this will require getting each game's
14178        termination from the PGN tags, as the game list builder does
14179        not process the game moves.  --mann
14180        */
14181     cmailMsgLoaded = TRUE;
14182     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14183
14184     /* Load first game in the file or popup game menu */
14185     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14186
14187 #endif /* !WIN32 */
14188     return;
14189 }
14190
14191 int
14192 RegisterMove ()
14193 {
14194     FILE *f;
14195     char string[MSG_SIZ];
14196
14197     if (   cmailMailedMove
14198         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14199         return TRUE;            /* Allow free viewing  */
14200     }
14201
14202     /* Unregister move to ensure that we don't leave RegisterMove        */
14203     /* with the move registered when the conditions for registering no   */
14204     /* longer hold                                                       */
14205     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14206         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14207         nCmailMovesRegistered --;
14208
14209         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14210           {
14211               free(cmailCommentList[lastLoadGameNumber - 1]);
14212               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14213           }
14214     }
14215
14216     if (cmailOldMove == -1) {
14217         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14218         return FALSE;
14219     }
14220
14221     if (currentMove > cmailOldMove + 1) {
14222         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14223         return FALSE;
14224     }
14225
14226     if (currentMove < cmailOldMove) {
14227         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14228         return FALSE;
14229     }
14230
14231     if (forwardMostMove > currentMove) {
14232         /* Silently truncate extra moves */
14233         TruncateGame();
14234     }
14235
14236     if (   (currentMove == cmailOldMove + 1)
14237         || (   (currentMove == cmailOldMove)
14238             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14239                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14240         if (gameInfo.result != GameUnfinished) {
14241             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14242         }
14243
14244         if (commentList[currentMove] != NULL) {
14245             cmailCommentList[lastLoadGameNumber - 1]
14246               = StrSave(commentList[currentMove]);
14247         }
14248         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14249
14250         if (appData.debugMode)
14251           fprintf(debugFP, "Saving %s for game %d\n",
14252                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14253
14254         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14255
14256         f = fopen(string, "w");
14257         if (appData.oldSaveStyle) {
14258             SaveGameOldStyle(f); /* also closes the file */
14259
14260             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14261             f = fopen(string, "w");
14262             SavePosition(f, 0, NULL); /* also closes the file */
14263         } else {
14264             fprintf(f, "{--------------\n");
14265             PrintPosition(f, currentMove);
14266             fprintf(f, "--------------}\n\n");
14267
14268             SaveGame(f, 0, NULL); /* also closes the file*/
14269         }
14270
14271         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14272         nCmailMovesRegistered ++;
14273     } else if (nCmailGames == 1) {
14274         DisplayError(_("You have not made a move yet"), 0);
14275         return FALSE;
14276     }
14277
14278     return TRUE;
14279 }
14280
14281 void
14282 MailMoveEvent ()
14283 {
14284 #if !WIN32
14285     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14286     FILE *commandOutput;
14287     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14288     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14289     int nBuffers;
14290     int i;
14291     int archived;
14292     char *arcDir;
14293
14294     if (! cmailMsgLoaded) {
14295         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14296         return;
14297     }
14298
14299     if (nCmailGames == nCmailResults) {
14300         DisplayError(_("No unfinished games"), 0);
14301         return;
14302     }
14303
14304 #if CMAIL_PROHIBIT_REMAIL
14305     if (cmailMailedMove) {
14306       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);
14307         DisplayError(msg, 0);
14308         return;
14309     }
14310 #endif
14311
14312     if (! (cmailMailedMove || RegisterMove())) return;
14313
14314     if (   cmailMailedMove
14315         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14316       snprintf(string, MSG_SIZ, partCommandString,
14317                appData.debugMode ? " -v" : "", appData.cmailGameName);
14318         commandOutput = popen(string, "r");
14319
14320         if (commandOutput == NULL) {
14321             DisplayError(_("Failed to invoke cmail"), 0);
14322         } else {
14323             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14324                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14325             }
14326             if (nBuffers > 1) {
14327                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14328                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14329                 nBytes = MSG_SIZ - 1;
14330             } else {
14331                 (void) memcpy(msg, buffer, nBytes);
14332             }
14333             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14334
14335             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14336                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14337
14338                 archived = TRUE;
14339                 for (i = 0; i < nCmailGames; i ++) {
14340                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14341                         archived = FALSE;
14342                     }
14343                 }
14344                 if (   archived
14345                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14346                         != NULL)) {
14347                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14348                            arcDir,
14349                            appData.cmailGameName,
14350                            gameInfo.date);
14351                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14352                     cmailMsgLoaded = FALSE;
14353                 }
14354             }
14355
14356             DisplayInformation(msg);
14357             pclose(commandOutput);
14358         }
14359     } else {
14360         if ((*cmailMsg) != '\0') {
14361             DisplayInformation(cmailMsg);
14362         }
14363     }
14364
14365     return;
14366 #endif /* !WIN32 */
14367 }
14368
14369 char *
14370 CmailMsg ()
14371 {
14372 #if WIN32
14373     return NULL;
14374 #else
14375     int  prependComma = 0;
14376     char number[5];
14377     char string[MSG_SIZ];       /* Space for game-list */
14378     int  i;
14379
14380     if (!cmailMsgLoaded) return "";
14381
14382     if (cmailMailedMove) {
14383       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14384     } else {
14385         /* Create a list of games left */
14386       snprintf(string, MSG_SIZ, "[");
14387         for (i = 0; i < nCmailGames; i ++) {
14388             if (! (   cmailMoveRegistered[i]
14389                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14390                 if (prependComma) {
14391                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14392                 } else {
14393                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14394                     prependComma = 1;
14395                 }
14396
14397                 strcat(string, number);
14398             }
14399         }
14400         strcat(string, "]");
14401
14402         if (nCmailMovesRegistered + nCmailResults == 0) {
14403             switch (nCmailGames) {
14404               case 1:
14405                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14406                 break;
14407
14408               case 2:
14409                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14410                 break;
14411
14412               default:
14413                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14414                          nCmailGames);
14415                 break;
14416             }
14417         } else {
14418             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14419               case 1:
14420                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14421                          string);
14422                 break;
14423
14424               case 0:
14425                 if (nCmailResults == nCmailGames) {
14426                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14427                 } else {
14428                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14429                 }
14430                 break;
14431
14432               default:
14433                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14434                          string);
14435             }
14436         }
14437     }
14438     return cmailMsg;
14439 #endif /* WIN32 */
14440 }
14441
14442 void
14443 ResetGameEvent ()
14444 {
14445     if (gameMode == Training)
14446       SetTrainingModeOff();
14447
14448     Reset(TRUE, TRUE);
14449     cmailMsgLoaded = FALSE;
14450     if (appData.icsActive) {
14451       SendToICS(ics_prefix);
14452       SendToICS("refresh\n");
14453     }
14454 }
14455
14456 void
14457 ExitEvent (int status)
14458 {
14459     exiting++;
14460     if (exiting > 2) {
14461       /* Give up on clean exit */
14462       exit(status);
14463     }
14464     if (exiting > 1) {
14465       /* Keep trying for clean exit */
14466       return;
14467     }
14468
14469     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14470     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14471
14472     if (telnetISR != NULL) {
14473       RemoveInputSource(telnetISR);
14474     }
14475     if (icsPR != NoProc) {
14476       DestroyChildProcess(icsPR, TRUE);
14477     }
14478
14479     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14480     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14481
14482     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14483     /* make sure this other one finishes before killing it!                  */
14484     if(endingGame) { int count = 0;
14485         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14486         while(endingGame && count++ < 10) DoSleep(1);
14487         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14488     }
14489
14490     /* Kill off chess programs */
14491     if (first.pr != NoProc) {
14492         ExitAnalyzeMode();
14493
14494         DoSleep( appData.delayBeforeQuit );
14495         SendToProgram("quit\n", &first);
14496         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14497     }
14498     if (second.pr != NoProc) {
14499         DoSleep( appData.delayBeforeQuit );
14500         SendToProgram("quit\n", &second);
14501         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14502     }
14503     if (first.isr != NULL) {
14504         RemoveInputSource(first.isr);
14505     }
14506     if (second.isr != NULL) {
14507         RemoveInputSource(second.isr);
14508     }
14509
14510     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14511     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14512
14513     ShutDownFrontEnd();
14514     exit(status);
14515 }
14516
14517 void
14518 PauseEngine (ChessProgramState *cps)
14519 {
14520     SendToProgram("pause\n", cps);
14521     cps->pause = 2;
14522 }
14523
14524 void
14525 UnPauseEngine (ChessProgramState *cps)
14526 {
14527     SendToProgram("resume\n", cps);
14528     cps->pause = 1;
14529 }
14530
14531 void
14532 PauseEvent ()
14533 {
14534     if (appData.debugMode)
14535         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14536     if (pausing) {
14537         pausing = FALSE;
14538         ModeHighlight();
14539         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14540             StartClocks();
14541             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14542                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14543                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14544             }
14545             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14546             HandleMachineMove(stashedInputMove, stalledEngine);
14547             stalledEngine = NULL;
14548             return;
14549         }
14550         if (gameMode == MachinePlaysWhite ||
14551             gameMode == TwoMachinesPlay   ||
14552             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14553             if(first.pause)  UnPauseEngine(&first);
14554             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14555             if(second.pause) UnPauseEngine(&second);
14556             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14557             StartClocks();
14558         } else {
14559             DisplayBothClocks();
14560         }
14561         if (gameMode == PlayFromGameFile) {
14562             if (appData.timeDelay >= 0)
14563                 AutoPlayGameLoop();
14564         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14565             Reset(FALSE, TRUE);
14566             SendToICS(ics_prefix);
14567             SendToICS("refresh\n");
14568         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14569             ForwardInner(forwardMostMove);
14570         }
14571         pauseExamInvalid = FALSE;
14572     } else {
14573         switch (gameMode) {
14574           default:
14575             return;
14576           case IcsExamining:
14577             pauseExamForwardMostMove = forwardMostMove;
14578             pauseExamInvalid = FALSE;
14579             /* fall through */
14580           case IcsObserving:
14581           case IcsPlayingWhite:
14582           case IcsPlayingBlack:
14583             pausing = TRUE;
14584             ModeHighlight();
14585             return;
14586           case PlayFromGameFile:
14587             (void) StopLoadGameTimer();
14588             pausing = TRUE;
14589             ModeHighlight();
14590             break;
14591           case BeginningOfGame:
14592             if (appData.icsActive) return;
14593             /* else fall through */
14594           case MachinePlaysWhite:
14595           case MachinePlaysBlack:
14596           case TwoMachinesPlay:
14597             if (forwardMostMove == 0)
14598               return;           /* don't pause if no one has moved */
14599             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14600                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14601                 if(onMove->pause) {           // thinking engine can be paused
14602                     PauseEngine(onMove);      // do it
14603                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14604                         PauseEngine(onMove->other);
14605                     else
14606                         SendToProgram("easy\n", onMove->other);
14607                     StopClocks();
14608                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14609             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14610                 if(first.pause) {
14611                     PauseEngine(&first);
14612                     StopClocks();
14613                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14614             } else { // human on move, pause pondering by either method
14615                 if(first.pause)
14616                     PauseEngine(&first);
14617                 else if(appData.ponderNextMove)
14618                     SendToProgram("easy\n", &first);
14619                 StopClocks();
14620             }
14621             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14622           case AnalyzeMode:
14623             pausing = TRUE;
14624             ModeHighlight();
14625             break;
14626         }
14627     }
14628 }
14629
14630 void
14631 EditCommentEvent ()
14632 {
14633     char title[MSG_SIZ];
14634
14635     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14636       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14637     } else {
14638       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14639                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14640                parseList[currentMove - 1]);
14641     }
14642
14643     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14644 }
14645
14646
14647 void
14648 EditTagsEvent ()
14649 {
14650     char *tags = PGNTags(&gameInfo);
14651     bookUp = FALSE;
14652     EditTagsPopUp(tags, NULL);
14653     free(tags);
14654 }
14655
14656 void
14657 ToggleSecond ()
14658 {
14659   if(second.analyzing) {
14660     SendToProgram("exit\n", &second);
14661     second.analyzing = FALSE;
14662   } else {
14663     if (second.pr == NoProc) StartChessProgram(&second);
14664     InitChessProgram(&second, FALSE);
14665     FeedMovesToProgram(&second, currentMove);
14666
14667     SendToProgram("analyze\n", &second);
14668     second.analyzing = TRUE;
14669   }
14670 }
14671
14672 /* Toggle ShowThinking */
14673 void
14674 ToggleShowThinking()
14675 {
14676   appData.showThinking = !appData.showThinking;
14677   ShowThinkingEvent();
14678 }
14679
14680 int
14681 AnalyzeModeEvent ()
14682 {
14683     char buf[MSG_SIZ];
14684
14685     if (!first.analysisSupport) {
14686       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14687       DisplayError(buf, 0);
14688       return 0;
14689     }
14690     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14691     if (appData.icsActive) {
14692         if (gameMode != IcsObserving) {
14693           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14694             DisplayError(buf, 0);
14695             /* secure check */
14696             if (appData.icsEngineAnalyze) {
14697                 if (appData.debugMode)
14698                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14699                 ExitAnalyzeMode();
14700                 ModeHighlight();
14701             }
14702             return 0;
14703         }
14704         /* if enable, user wants to disable icsEngineAnalyze */
14705         if (appData.icsEngineAnalyze) {
14706                 ExitAnalyzeMode();
14707                 ModeHighlight();
14708                 return 0;
14709         }
14710         appData.icsEngineAnalyze = TRUE;
14711         if (appData.debugMode)
14712             fprintf(debugFP, "ICS engine analyze starting... \n");
14713     }
14714
14715     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14716     if (appData.noChessProgram || gameMode == AnalyzeMode)
14717       return 0;
14718
14719     if (gameMode != AnalyzeFile) {
14720         if (!appData.icsEngineAnalyze) {
14721                EditGameEvent();
14722                if (gameMode != EditGame) return 0;
14723         }
14724         if (!appData.showThinking) ToggleShowThinking();
14725         ResurrectChessProgram();
14726         SendToProgram("analyze\n", &first);
14727         first.analyzing = TRUE;
14728         /*first.maybeThinking = TRUE;*/
14729         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14730         EngineOutputPopUp();
14731     }
14732     if (!appData.icsEngineAnalyze) {
14733         gameMode = AnalyzeMode;
14734         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14735     }
14736     pausing = FALSE;
14737     ModeHighlight();
14738     SetGameInfo();
14739
14740     StartAnalysisClock();
14741     GetTimeMark(&lastNodeCountTime);
14742     lastNodeCount = 0;
14743     return 1;
14744 }
14745
14746 void
14747 AnalyzeFileEvent ()
14748 {
14749     if (appData.noChessProgram || gameMode == AnalyzeFile)
14750       return;
14751
14752     if (!first.analysisSupport) {
14753       char buf[MSG_SIZ];
14754       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14755       DisplayError(buf, 0);
14756       return;
14757     }
14758
14759     if (gameMode != AnalyzeMode) {
14760         keepInfo = 1; // mere annotating should not alter PGN tags
14761         EditGameEvent();
14762         keepInfo = 0;
14763         if (gameMode != EditGame) return;
14764         if (!appData.showThinking) ToggleShowThinking();
14765         ResurrectChessProgram();
14766         SendToProgram("analyze\n", &first);
14767         first.analyzing = TRUE;
14768         /*first.maybeThinking = TRUE;*/
14769         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14770         EngineOutputPopUp();
14771     }
14772     gameMode = AnalyzeFile;
14773     pausing = FALSE;
14774     ModeHighlight();
14775
14776     StartAnalysisClock();
14777     GetTimeMark(&lastNodeCountTime);
14778     lastNodeCount = 0;
14779     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14780     AnalysisPeriodicEvent(1);
14781 }
14782
14783 void
14784 MachineWhiteEvent ()
14785 {
14786     char buf[MSG_SIZ];
14787     char *bookHit = NULL;
14788
14789     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14790       return;
14791
14792
14793     if (gameMode == PlayFromGameFile ||
14794         gameMode == TwoMachinesPlay  ||
14795         gameMode == Training         ||
14796         gameMode == AnalyzeMode      ||
14797         gameMode == EndOfGame)
14798         EditGameEvent();
14799
14800     if (gameMode == EditPosition)
14801         EditPositionDone(TRUE);
14802
14803     if (!WhiteOnMove(currentMove)) {
14804         DisplayError(_("It is not White's turn"), 0);
14805         return;
14806     }
14807
14808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14809       ExitAnalyzeMode();
14810
14811     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14812         gameMode == AnalyzeFile)
14813         TruncateGame();
14814
14815     ResurrectChessProgram();    /* in case it isn't running */
14816     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14817         gameMode = MachinePlaysWhite;
14818         ResetClocks();
14819     } else
14820     gameMode = MachinePlaysWhite;
14821     pausing = FALSE;
14822     ModeHighlight();
14823     SetGameInfo();
14824     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14825     DisplayTitle(buf);
14826     if (first.sendName) {
14827       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14828       SendToProgram(buf, &first);
14829     }
14830     if (first.sendTime) {
14831       if (first.useColors) {
14832         SendToProgram("black\n", &first); /*gnu kludge*/
14833       }
14834       SendTimeRemaining(&first, TRUE);
14835     }
14836     if (first.useColors) {
14837       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14838     }
14839     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14840     SetMachineThinkingEnables();
14841     first.maybeThinking = TRUE;
14842     StartClocks();
14843     firstMove = FALSE;
14844
14845     if (appData.autoFlipView && !flipView) {
14846       flipView = !flipView;
14847       DrawPosition(FALSE, NULL);
14848       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14849     }
14850
14851     if(bookHit) { // [HGM] book: simulate book reply
14852         static char bookMove[MSG_SIZ]; // a bit generous?
14853
14854         programStats.nodes = programStats.depth = programStats.time =
14855         programStats.score = programStats.got_only_move = 0;
14856         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14857
14858         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14859         strcat(bookMove, bookHit);
14860         HandleMachineMove(bookMove, &first);
14861     }
14862 }
14863
14864 void
14865 MachineBlackEvent ()
14866 {
14867   char buf[MSG_SIZ];
14868   char *bookHit = NULL;
14869
14870     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14871         return;
14872
14873
14874     if (gameMode == PlayFromGameFile ||
14875         gameMode == TwoMachinesPlay  ||
14876         gameMode == Training         ||
14877         gameMode == AnalyzeMode      ||
14878         gameMode == EndOfGame)
14879         EditGameEvent();
14880
14881     if (gameMode == EditPosition)
14882         EditPositionDone(TRUE);
14883
14884     if (WhiteOnMove(currentMove)) {
14885         DisplayError(_("It is not Black's turn"), 0);
14886         return;
14887     }
14888
14889     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14890       ExitAnalyzeMode();
14891
14892     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14893         gameMode == AnalyzeFile)
14894         TruncateGame();
14895
14896     ResurrectChessProgram();    /* in case it isn't running */
14897     gameMode = MachinePlaysBlack;
14898     pausing = FALSE;
14899     ModeHighlight();
14900     SetGameInfo();
14901     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14902     DisplayTitle(buf);
14903     if (first.sendName) {
14904       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14905       SendToProgram(buf, &first);
14906     }
14907     if (first.sendTime) {
14908       if (first.useColors) {
14909         SendToProgram("white\n", &first); /*gnu kludge*/
14910       }
14911       SendTimeRemaining(&first, FALSE);
14912     }
14913     if (first.useColors) {
14914       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14915     }
14916     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14917     SetMachineThinkingEnables();
14918     first.maybeThinking = TRUE;
14919     StartClocks();
14920
14921     if (appData.autoFlipView && flipView) {
14922       flipView = !flipView;
14923       DrawPosition(FALSE, NULL);
14924       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14925     }
14926     if(bookHit) { // [HGM] book: simulate book reply
14927         static char bookMove[MSG_SIZ]; // a bit generous?
14928
14929         programStats.nodes = programStats.depth = programStats.time =
14930         programStats.score = programStats.got_only_move = 0;
14931         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14932
14933         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14934         strcat(bookMove, bookHit);
14935         HandleMachineMove(bookMove, &first);
14936     }
14937 }
14938
14939
14940 void
14941 DisplayTwoMachinesTitle ()
14942 {
14943     char buf[MSG_SIZ];
14944     if (appData.matchGames > 0) {
14945         if(appData.tourneyFile[0]) {
14946           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14947                    gameInfo.white, _("vs."), gameInfo.black,
14948                    nextGame+1, appData.matchGames+1,
14949                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14950         } else
14951         if (first.twoMachinesColor[0] == 'w') {
14952           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14953                    gameInfo.white, _("vs."),  gameInfo.black,
14954                    first.matchWins, second.matchWins,
14955                    matchGame - 1 - (first.matchWins + second.matchWins));
14956         } else {
14957           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14958                    gameInfo.white, _("vs."), gameInfo.black,
14959                    second.matchWins, first.matchWins,
14960                    matchGame - 1 - (first.matchWins + second.matchWins));
14961         }
14962     } else {
14963       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14964     }
14965     DisplayTitle(buf);
14966 }
14967
14968 void
14969 SettingsMenuIfReady ()
14970 {
14971   if (second.lastPing != second.lastPong) {
14972     DisplayMessage("", _("Waiting for second chess program"));
14973     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14974     return;
14975   }
14976   ThawUI();
14977   DisplayMessage("", "");
14978   SettingsPopUp(&second);
14979 }
14980
14981 int
14982 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14983 {
14984     char buf[MSG_SIZ];
14985     if (cps->pr == NoProc) {
14986         StartChessProgram(cps);
14987         if (cps->protocolVersion == 1) {
14988           retry();
14989           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14990         } else {
14991           /* kludge: allow timeout for initial "feature" command */
14992           if(retry != TwoMachinesEventIfReady) FreezeUI();
14993           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14994           DisplayMessage("", buf);
14995           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14996         }
14997         return 1;
14998     }
14999     return 0;
15000 }
15001
15002 void
15003 TwoMachinesEvent P((void))
15004 {
15005     int i;
15006     char buf[MSG_SIZ];
15007     ChessProgramState *onmove;
15008     char *bookHit = NULL;
15009     static int stalling = 0;
15010     TimeMark now;
15011     long wait;
15012
15013     if (appData.noChessProgram) return;
15014
15015     switch (gameMode) {
15016       case TwoMachinesPlay:
15017         return;
15018       case MachinePlaysWhite:
15019       case MachinePlaysBlack:
15020         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15021             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15022             return;
15023         }
15024         /* fall through */
15025       case BeginningOfGame:
15026       case PlayFromGameFile:
15027       case EndOfGame:
15028         EditGameEvent();
15029         if (gameMode != EditGame) return;
15030         break;
15031       case EditPosition:
15032         EditPositionDone(TRUE);
15033         break;
15034       case AnalyzeMode:
15035       case AnalyzeFile:
15036         ExitAnalyzeMode();
15037         break;
15038       case EditGame:
15039       default:
15040         break;
15041     }
15042
15043 //    forwardMostMove = currentMove;
15044     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15045     startingEngine = TRUE;
15046
15047     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15048
15049     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15050     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15051       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15052       return;
15053     }
15054   if(!appData.epd) {
15055     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15056
15057     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15058                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15059         startingEngine = matchMode = FALSE;
15060         DisplayError("second engine does not play this", 0);
15061         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15062         EditGameEvent(); // switch back to EditGame mode
15063         return;
15064     }
15065
15066     if(!stalling) {
15067       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15068       SendToProgram("force\n", &second);
15069       stalling = 1;
15070       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15071       return;
15072     }
15073   }
15074     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15075     if(appData.matchPause>10000 || appData.matchPause<10)
15076                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15077     wait = SubtractTimeMarks(&now, &pauseStart);
15078     if(wait < appData.matchPause) {
15079         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15080         return;
15081     }
15082     // we are now committed to starting the game
15083     stalling = 0;
15084     DisplayMessage("", "");
15085   if(!appData.epd) {
15086     if (startedFromSetupPosition) {
15087         SendBoard(&second, backwardMostMove);
15088     if (appData.debugMode) {
15089         fprintf(debugFP, "Two Machines\n");
15090     }
15091     }
15092     for (i = backwardMostMove; i < forwardMostMove; i++) {
15093         SendMoveToProgram(i, &second);
15094     }
15095   }
15096
15097     gameMode = TwoMachinesPlay;
15098     pausing = startingEngine = FALSE;
15099     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15100     SetGameInfo();
15101     DisplayTwoMachinesTitle();
15102     firstMove = TRUE;
15103     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15104         onmove = &first;
15105     } else {
15106         onmove = &second;
15107     }
15108     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15109     SendToProgram(first.computerString, &first);
15110     if (first.sendName) {
15111       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15112       SendToProgram(buf, &first);
15113     }
15114   if(!appData.epd) {
15115     SendToProgram(second.computerString, &second);
15116     if (second.sendName) {
15117       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15118       SendToProgram(buf, &second);
15119     }
15120   }
15121
15122     ResetClocks();
15123     if (!first.sendTime || !second.sendTime) {
15124         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15125         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15126     }
15127     if (onmove->sendTime) {
15128       if (onmove->useColors) {
15129         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15130       }
15131       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15132     }
15133     if (onmove->useColors) {
15134       SendToProgram(onmove->twoMachinesColor, onmove);
15135     }
15136     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15137 //    SendToProgram("go\n", onmove);
15138     onmove->maybeThinking = TRUE;
15139     SetMachineThinkingEnables();
15140
15141     StartClocks();
15142
15143     if(bookHit) { // [HGM] book: simulate book reply
15144         static char bookMove[MSG_SIZ]; // a bit generous?
15145
15146         programStats.nodes = programStats.depth = programStats.time =
15147         programStats.score = programStats.got_only_move = 0;
15148         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15149
15150         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15151         strcat(bookMove, bookHit);
15152         savedMessage = bookMove; // args for deferred call
15153         savedState = onmove;
15154         ScheduleDelayedEvent(DeferredBookMove, 1);
15155     }
15156 }
15157
15158 void
15159 TrainingEvent ()
15160 {
15161     if (gameMode == Training) {
15162       SetTrainingModeOff();
15163       gameMode = PlayFromGameFile;
15164       DisplayMessage("", _("Training mode off"));
15165     } else {
15166       gameMode = Training;
15167       animateTraining = appData.animate;
15168
15169       /* make sure we are not already at the end of the game */
15170       if (currentMove < forwardMostMove) {
15171         SetTrainingModeOn();
15172         DisplayMessage("", _("Training mode on"));
15173       } else {
15174         gameMode = PlayFromGameFile;
15175         DisplayError(_("Already at end of game"), 0);
15176       }
15177     }
15178     ModeHighlight();
15179 }
15180
15181 void
15182 IcsClientEvent ()
15183 {
15184     if (!appData.icsActive) return;
15185     switch (gameMode) {
15186       case IcsPlayingWhite:
15187       case IcsPlayingBlack:
15188       case IcsObserving:
15189       case IcsIdle:
15190       case BeginningOfGame:
15191       case IcsExamining:
15192         return;
15193
15194       case EditGame:
15195         break;
15196
15197       case EditPosition:
15198         EditPositionDone(TRUE);
15199         break;
15200
15201       case AnalyzeMode:
15202       case AnalyzeFile:
15203         ExitAnalyzeMode();
15204         break;
15205
15206       default:
15207         EditGameEvent();
15208         break;
15209     }
15210
15211     gameMode = IcsIdle;
15212     ModeHighlight();
15213     return;
15214 }
15215
15216 void
15217 EditGameEvent ()
15218 {
15219     int i;
15220
15221     switch (gameMode) {
15222       case Training:
15223         SetTrainingModeOff();
15224         break;
15225       case MachinePlaysWhite:
15226       case MachinePlaysBlack:
15227       case BeginningOfGame:
15228         SendToProgram("force\n", &first);
15229         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15230             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15231                 char buf[MSG_SIZ];
15232                 abortEngineThink = TRUE;
15233                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15234                 SendToProgram(buf, &first);
15235                 DisplayMessage("Aborting engine think", "");
15236                 FreezeUI();
15237             }
15238         }
15239         SetUserThinkingEnables();
15240         break;
15241       case PlayFromGameFile:
15242         (void) StopLoadGameTimer();
15243         if (gameFileFP != NULL) {
15244             gameFileFP = NULL;
15245         }
15246         break;
15247       case EditPosition:
15248         EditPositionDone(TRUE);
15249         break;
15250       case AnalyzeMode:
15251       case AnalyzeFile:
15252         ExitAnalyzeMode();
15253         SendToProgram("force\n", &first);
15254         break;
15255       case TwoMachinesPlay:
15256         GameEnds(EndOfFile, NULL, GE_PLAYER);
15257         ResurrectChessProgram();
15258         SetUserThinkingEnables();
15259         break;
15260       case EndOfGame:
15261         ResurrectChessProgram();
15262         break;
15263       case IcsPlayingBlack:
15264       case IcsPlayingWhite:
15265         DisplayError(_("Warning: You are still playing a game"), 0);
15266         break;
15267       case IcsObserving:
15268         DisplayError(_("Warning: You are still observing a game"), 0);
15269         break;
15270       case IcsExamining:
15271         DisplayError(_("Warning: You are still examining a game"), 0);
15272         break;
15273       case IcsIdle:
15274         break;
15275       case EditGame:
15276       default:
15277         return;
15278     }
15279
15280     pausing = FALSE;
15281     StopClocks();
15282     first.offeredDraw = second.offeredDraw = 0;
15283
15284     if (gameMode == PlayFromGameFile) {
15285         whiteTimeRemaining = timeRemaining[0][currentMove];
15286         blackTimeRemaining = timeRemaining[1][currentMove];
15287         DisplayTitle("");
15288     }
15289
15290     if (gameMode == MachinePlaysWhite ||
15291         gameMode == MachinePlaysBlack ||
15292         gameMode == TwoMachinesPlay ||
15293         gameMode == EndOfGame) {
15294         i = forwardMostMove;
15295         while (i > currentMove) {
15296             SendToProgram("undo\n", &first);
15297             i--;
15298         }
15299         if(!adjustedClock) {
15300         whiteTimeRemaining = timeRemaining[0][currentMove];
15301         blackTimeRemaining = timeRemaining[1][currentMove];
15302         DisplayBothClocks();
15303         }
15304         if (whiteFlag || blackFlag) {
15305             whiteFlag = blackFlag = 0;
15306         }
15307         DisplayTitle("");
15308     }
15309
15310     gameMode = EditGame;
15311     ModeHighlight();
15312     SetGameInfo();
15313 }
15314
15315 void
15316 EditPositionEvent ()
15317 {
15318     int i;
15319     if (gameMode == EditPosition) {
15320         EditGameEvent();
15321         return;
15322     }
15323
15324     EditGameEvent();
15325     if (gameMode != EditGame) return;
15326
15327     gameMode = EditPosition;
15328     ModeHighlight();
15329     SetGameInfo();
15330     CopyBoard(rightsBoard, nullBoard);
15331     if (currentMove > 0)
15332       CopyBoard(boards[0], boards[currentMove]);
15333     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15334       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15335
15336     blackPlaysFirst = !WhiteOnMove(currentMove);
15337     ResetClocks();
15338     currentMove = forwardMostMove = backwardMostMove = 0;
15339     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15340     DisplayMove(-1);
15341     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15342 }
15343
15344 void
15345 ExitAnalyzeMode ()
15346 {
15347     /* [DM] icsEngineAnalyze - possible call from other functions */
15348     if (appData.icsEngineAnalyze) {
15349         appData.icsEngineAnalyze = FALSE;
15350
15351         DisplayMessage("",_("Close ICS engine analyze..."));
15352     }
15353     if (first.analysisSupport && first.analyzing) {
15354       SendToBoth("exit\n");
15355       first.analyzing = second.analyzing = FALSE;
15356     }
15357     thinkOutput[0] = NULLCHAR;
15358 }
15359
15360 void
15361 EditPositionDone (Boolean fakeRights)
15362 {
15363     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15364
15365     startedFromSetupPosition = TRUE;
15366     InitChessProgram(&first, FALSE);
15367     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15368       int r, f;
15369       boards[0][EP_STATUS] = EP_NONE;
15370       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15371       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15372         if(rightsBoard[r][f]) {
15373           ChessSquare p = boards[0][r][f];
15374           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15375           else if(p == king) boards[0][CASTLING][2] = f;
15376           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15377           else rightsBoard[r][f] = 2; // mark for second pass
15378         }
15379       }
15380       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15381         if(rightsBoard[r][f] == 2) {
15382           ChessSquare p = boards[0][r][f];
15383           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15384           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15385         }
15386       }
15387     }
15388     SendToProgram("force\n", &first);
15389     if (blackPlaysFirst) {
15390         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15391         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15392         currentMove = forwardMostMove = backwardMostMove = 1;
15393         CopyBoard(boards[1], boards[0]);
15394     } else {
15395         currentMove = forwardMostMove = backwardMostMove = 0;
15396     }
15397     SendBoard(&first, forwardMostMove);
15398     if (appData.debugMode) {
15399         fprintf(debugFP, "EditPosDone\n");
15400     }
15401     DisplayTitle("");
15402     DisplayMessage("", "");
15403     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15404     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15405     gameMode = EditGame;
15406     ModeHighlight();
15407     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15408     ClearHighlights(); /* [AS] */
15409 }
15410
15411 /* Pause for `ms' milliseconds */
15412 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15413 void
15414 TimeDelay (long ms)
15415 {
15416     TimeMark m1, m2;
15417
15418     GetTimeMark(&m1);
15419     do {
15420         GetTimeMark(&m2);
15421     } while (SubtractTimeMarks(&m2, &m1) < ms);
15422 }
15423
15424 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15425 void
15426 SendMultiLineToICS (char *buf)
15427 {
15428     char temp[MSG_SIZ+1], *p;
15429     int len;
15430
15431     len = strlen(buf);
15432     if (len > MSG_SIZ)
15433       len = MSG_SIZ;
15434
15435     strncpy(temp, buf, len);
15436     temp[len] = 0;
15437
15438     p = temp;
15439     while (*p) {
15440         if (*p == '\n' || *p == '\r')
15441           *p = ' ';
15442         ++p;
15443     }
15444
15445     strcat(temp, "\n");
15446     SendToICS(temp);
15447     SendToPlayer(temp, strlen(temp));
15448 }
15449
15450 void
15451 SetWhiteToPlayEvent ()
15452 {
15453     if (gameMode == EditPosition) {
15454         blackPlaysFirst = FALSE;
15455         DisplayBothClocks();    /* works because currentMove is 0 */
15456     } else if (gameMode == IcsExamining) {
15457         SendToICS(ics_prefix);
15458         SendToICS("tomove white\n");
15459     }
15460 }
15461
15462 void
15463 SetBlackToPlayEvent ()
15464 {
15465     if (gameMode == EditPosition) {
15466         blackPlaysFirst = TRUE;
15467         currentMove = 1;        /* kludge */
15468         DisplayBothClocks();
15469         currentMove = 0;
15470     } else if (gameMode == IcsExamining) {
15471         SendToICS(ics_prefix);
15472         SendToICS("tomove black\n");
15473     }
15474 }
15475
15476 void
15477 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15478 {
15479     char buf[MSG_SIZ];
15480     ChessSquare piece = boards[0][y][x];
15481     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15482     static int lastVariant;
15483     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15484
15485     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15486
15487     switch (selection) {
15488       case ClearBoard:
15489         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15490         MarkTargetSquares(1);
15491         CopyBoard(currentBoard, boards[0]);
15492         CopyBoard(menuBoard, initialPosition);
15493         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15494             SendToICS(ics_prefix);
15495             SendToICS("bsetup clear\n");
15496         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15497             SendToICS(ics_prefix);
15498             SendToICS("clearboard\n");
15499         } else {
15500             int nonEmpty = 0;
15501             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15502                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15503                 for (y = 0; y < BOARD_HEIGHT; y++) {
15504                     if (gameMode == IcsExamining) {
15505                         if (boards[currentMove][y][x] != EmptySquare) {
15506                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15507                                     AAA + x, ONE + y);
15508                             SendToICS(buf);
15509                         }
15510                     } else if(boards[0][y][x] != DarkSquare) {
15511                         if(boards[0][y][x] != p) nonEmpty++;
15512                         boards[0][y][x] = p;
15513                     }
15514                 }
15515             }
15516             CopyBoard(rightsBoard, nullBoard);
15517             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15518                 int r, i;
15519                 for(r = 0; r < BOARD_HEIGHT; r++) {
15520                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15521                     ChessSquare p = menuBoard[r][x];
15522                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15523                   }
15524                 }
15525                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15526                 DisplayMessage("Clicking clock again restores position", "");
15527                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15528                 if(!nonEmpty) { // asked to clear an empty board
15529                     CopyBoard(boards[0], menuBoard);
15530                 } else
15531                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15532                     CopyBoard(boards[0], initialPosition);
15533                 } else
15534                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15535                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15536                     CopyBoard(boards[0], erasedBoard);
15537                 } else
15538                     CopyBoard(erasedBoard, currentBoard);
15539
15540                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15541                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15542             }
15543         }
15544         if (gameMode == EditPosition) {
15545             DrawPosition(FALSE, boards[0]);
15546         }
15547         break;
15548
15549       case WhitePlay:
15550         SetWhiteToPlayEvent();
15551         break;
15552
15553       case BlackPlay:
15554         SetBlackToPlayEvent();
15555         break;
15556
15557       case EmptySquare:
15558         if (gameMode == IcsExamining) {
15559             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15560             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15561             SendToICS(buf);
15562         } else {
15563             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15564                 if(x == BOARD_LEFT-2) {
15565                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15566                     boards[0][y][1] = 0;
15567                 } else
15568                 if(x == BOARD_RGHT+1) {
15569                     if(y >= gameInfo.holdingsSize) break;
15570                     boards[0][y][BOARD_WIDTH-2] = 0;
15571                 } else break;
15572             }
15573             boards[0][y][x] = EmptySquare;
15574             DrawPosition(FALSE, boards[0]);
15575         }
15576         break;
15577
15578       case PromotePiece:
15579         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15580            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15581             selection = (ChessSquare) (PROMOTED(piece));
15582         } else if(piece == EmptySquare) selection = WhiteSilver;
15583         else selection = (ChessSquare)((int)piece - 1);
15584         goto defaultlabel;
15585
15586       case DemotePiece:
15587         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15588            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15589             selection = (ChessSquare) (DEMOTED(piece));
15590         } else if(piece == EmptySquare) selection = BlackSilver;
15591         else selection = (ChessSquare)((int)piece + 1);
15592         goto defaultlabel;
15593
15594       case WhiteQueen:
15595       case BlackQueen:
15596         if(gameInfo.variant == VariantShatranj ||
15597            gameInfo.variant == VariantXiangqi  ||
15598            gameInfo.variant == VariantCourier  ||
15599            gameInfo.variant == VariantASEAN    ||
15600            gameInfo.variant == VariantMakruk     )
15601             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15602         goto defaultlabel;
15603
15604       case WhiteRook:
15605         baseRank = 0;
15606       case BlackRook:
15607         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15608         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15609         goto defaultlabel;
15610
15611       case WhiteKing:
15612         baseRank = 0;
15613       case BlackKing:
15614         if(gameInfo.variant == VariantXiangqi)
15615             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15616         if(gameInfo.variant == VariantKnightmate)
15617             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15618         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15619       default:
15620         defaultlabel:
15621         if (gameMode == IcsExamining) {
15622             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15623             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15624                      PieceToChar(selection), AAA + x, ONE + y);
15625             SendToICS(buf);
15626         } else {
15627             rightsBoard[y][x] = hasRights;
15628             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15629                 int n;
15630                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15631                     n = PieceToNumber(selection - BlackPawn);
15632                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15633                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15634                     boards[0][BOARD_HEIGHT-1-n][1]++;
15635                 } else
15636                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15637                     n = PieceToNumber(selection);
15638                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15639                     boards[0][n][BOARD_WIDTH-1] = selection;
15640                     boards[0][n][BOARD_WIDTH-2]++;
15641                 }
15642             } else
15643             boards[0][y][x] = selection;
15644             DrawPosition(TRUE, boards[0]);
15645             ClearHighlights();
15646             fromX = fromY = -1;
15647         }
15648         break;
15649     }
15650 }
15651
15652
15653 void
15654 DropMenuEvent (ChessSquare selection, int x, int y)
15655 {
15656     ChessMove moveType;
15657
15658     switch (gameMode) {
15659       case IcsPlayingWhite:
15660       case MachinePlaysBlack:
15661         if (!WhiteOnMove(currentMove)) {
15662             DisplayMoveError(_("It is Black's turn"));
15663             return;
15664         }
15665         moveType = WhiteDrop;
15666         break;
15667       case IcsPlayingBlack:
15668       case MachinePlaysWhite:
15669         if (WhiteOnMove(currentMove)) {
15670             DisplayMoveError(_("It is White's turn"));
15671             return;
15672         }
15673         moveType = BlackDrop;
15674         break;
15675       case EditGame:
15676         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15677         break;
15678       default:
15679         return;
15680     }
15681
15682     if (moveType == BlackDrop && selection < BlackPawn) {
15683       selection = (ChessSquare) ((int) selection
15684                                  + (int) BlackPawn - (int) WhitePawn);
15685     }
15686     if (boards[currentMove][y][x] != EmptySquare) {
15687         DisplayMoveError(_("That square is occupied"));
15688         return;
15689     }
15690
15691     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15692 }
15693
15694 void
15695 AcceptEvent ()
15696 {
15697     /* Accept a pending offer of any kind from opponent */
15698
15699     if (appData.icsActive) {
15700         SendToICS(ics_prefix);
15701         SendToICS("accept\n");
15702     } else if (cmailMsgLoaded) {
15703         if (currentMove == cmailOldMove &&
15704             commentList[cmailOldMove] != NULL &&
15705             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15706                    "Black offers a draw" : "White offers a draw")) {
15707             TruncateGame();
15708             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15709             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15710         } else {
15711             DisplayError(_("There is no pending offer on this move"), 0);
15712             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15713         }
15714     } else {
15715         /* Not used for offers from chess program */
15716     }
15717 }
15718
15719 void
15720 DeclineEvent ()
15721 {
15722     /* Decline a pending offer of any kind from opponent */
15723
15724     if (appData.icsActive) {
15725         SendToICS(ics_prefix);
15726         SendToICS("decline\n");
15727     } else if (cmailMsgLoaded) {
15728         if (currentMove == cmailOldMove &&
15729             commentList[cmailOldMove] != NULL &&
15730             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15731                    "Black offers a draw" : "White offers a draw")) {
15732 #ifdef NOTDEF
15733             AppendComment(cmailOldMove, "Draw declined", TRUE);
15734             DisplayComment(cmailOldMove - 1, "Draw declined");
15735 #endif /*NOTDEF*/
15736         } else {
15737             DisplayError(_("There is no pending offer on this move"), 0);
15738         }
15739     } else {
15740         /* Not used for offers from chess program */
15741     }
15742 }
15743
15744 void
15745 RematchEvent ()
15746 {
15747     /* Issue ICS rematch command */
15748     if (appData.icsActive) {
15749         SendToICS(ics_prefix);
15750         SendToICS("rematch\n");
15751     }
15752 }
15753
15754 void
15755 CallFlagEvent ()
15756 {
15757     /* Call your opponent's flag (claim a win on time) */
15758     if (appData.icsActive) {
15759         SendToICS(ics_prefix);
15760         SendToICS("flag\n");
15761     } else {
15762         switch (gameMode) {
15763           default:
15764             return;
15765           case MachinePlaysWhite:
15766             if (whiteFlag) {
15767                 if (blackFlag)
15768                   GameEnds(GameIsDrawn, "Both players ran out of time",
15769                            GE_PLAYER);
15770                 else
15771                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15772             } else {
15773                 DisplayError(_("Your opponent is not out of time"), 0);
15774             }
15775             break;
15776           case MachinePlaysBlack:
15777             if (blackFlag) {
15778                 if (whiteFlag)
15779                   GameEnds(GameIsDrawn, "Both players ran out of time",
15780                            GE_PLAYER);
15781                 else
15782                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15783             } else {
15784                 DisplayError(_("Your opponent is not out of time"), 0);
15785             }
15786             break;
15787         }
15788     }
15789 }
15790
15791 void
15792 ClockClick (int which)
15793 {       // [HGM] code moved to back-end from winboard.c
15794         if(which) { // black clock
15795           if (gameMode == EditPosition || gameMode == IcsExamining) {
15796             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15797             SetBlackToPlayEvent();
15798           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15799                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15800           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15801           } else if (shiftKey) {
15802             AdjustClock(which, -1);
15803           } else if (gameMode == IcsPlayingWhite ||
15804                      gameMode == MachinePlaysBlack) {
15805             CallFlagEvent();
15806           }
15807         } else { // white clock
15808           if (gameMode == EditPosition || gameMode == IcsExamining) {
15809             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15810             SetWhiteToPlayEvent();
15811           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15812                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15813           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15814           } else if (shiftKey) {
15815             AdjustClock(which, -1);
15816           } else if (gameMode == IcsPlayingBlack ||
15817                    gameMode == MachinePlaysWhite) {
15818             CallFlagEvent();
15819           }
15820         }
15821 }
15822
15823 void
15824 DrawEvent ()
15825 {
15826     /* Offer draw or accept pending draw offer from opponent */
15827
15828     if (appData.icsActive) {
15829         /* Note: tournament rules require draw offers to be
15830            made after you make your move but before you punch
15831            your clock.  Currently ICS doesn't let you do that;
15832            instead, you immediately punch your clock after making
15833            a move, but you can offer a draw at any time. */
15834
15835         SendToICS(ics_prefix);
15836         SendToICS("draw\n");
15837         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15838     } else if (cmailMsgLoaded) {
15839         if (currentMove == cmailOldMove &&
15840             commentList[cmailOldMove] != NULL &&
15841             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15842                    "Black offers a draw" : "White offers a draw")) {
15843             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15844             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15845         } else if (currentMove == cmailOldMove + 1) {
15846             char *offer = WhiteOnMove(cmailOldMove) ?
15847               "White offers a draw" : "Black offers a draw";
15848             AppendComment(currentMove, offer, TRUE);
15849             DisplayComment(currentMove - 1, offer);
15850             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15851         } else {
15852             DisplayError(_("You must make your move before offering a draw"), 0);
15853             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15854         }
15855     } else if (first.offeredDraw) {
15856         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15857     } else {
15858         if (first.sendDrawOffers) {
15859             SendToProgram("draw\n", &first);
15860             userOfferedDraw = TRUE;
15861         }
15862     }
15863 }
15864
15865 void
15866 AdjournEvent ()
15867 {
15868     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15869
15870     if (appData.icsActive) {
15871         SendToICS(ics_prefix);
15872         SendToICS("adjourn\n");
15873     } else {
15874         /* Currently GNU Chess doesn't offer or accept Adjourns */
15875     }
15876 }
15877
15878
15879 void
15880 AbortEvent ()
15881 {
15882     /* Offer Abort or accept pending Abort offer from opponent */
15883
15884     if (appData.icsActive) {
15885         SendToICS(ics_prefix);
15886         SendToICS("abort\n");
15887     } else {
15888         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15889     }
15890 }
15891
15892 void
15893 ResignEvent ()
15894 {
15895     /* Resign.  You can do this even if it's not your turn. */
15896
15897     if (appData.icsActive) {
15898         SendToICS(ics_prefix);
15899         SendToICS("resign\n");
15900     } else {
15901         switch (gameMode) {
15902           case MachinePlaysWhite:
15903             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15904             break;
15905           case MachinePlaysBlack:
15906             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15907             break;
15908           case EditGame:
15909             if (cmailMsgLoaded) {
15910                 TruncateGame();
15911                 if (WhiteOnMove(cmailOldMove)) {
15912                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15913                 } else {
15914                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15915                 }
15916                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15917             }
15918             break;
15919           default:
15920             break;
15921         }
15922     }
15923 }
15924
15925
15926 void
15927 StopObservingEvent ()
15928 {
15929     /* Stop observing current games */
15930     SendToICS(ics_prefix);
15931     SendToICS("unobserve\n");
15932 }
15933
15934 void
15935 StopExaminingEvent ()
15936 {
15937     /* Stop observing current game */
15938     SendToICS(ics_prefix);
15939     SendToICS("unexamine\n");
15940 }
15941
15942 void
15943 ForwardInner (int target)
15944 {
15945     int limit; int oldSeekGraphUp = seekGraphUp;
15946
15947     if (appData.debugMode)
15948         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15949                 target, currentMove, forwardMostMove);
15950
15951     if (gameMode == EditPosition)
15952       return;
15953
15954     seekGraphUp = FALSE;
15955     MarkTargetSquares(1);
15956     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15957
15958     if (gameMode == PlayFromGameFile && !pausing)
15959       PauseEvent();
15960
15961     if (gameMode == IcsExamining && pausing)
15962       limit = pauseExamForwardMostMove;
15963     else
15964       limit = forwardMostMove;
15965
15966     if (target > limit) target = limit;
15967
15968     if (target > 0 && moveList[target - 1][0]) {
15969         int fromX, fromY, toX, toY;
15970         toX = moveList[target - 1][2] - AAA;
15971         toY = moveList[target - 1][3] - ONE;
15972         if (moveList[target - 1][1] == '@') {
15973             if (appData.highlightLastMove) {
15974                 SetHighlights(-1, -1, toX, toY);
15975             }
15976         } else {
15977             fromX = moveList[target - 1][0] - AAA;
15978             fromY = moveList[target - 1][1] - ONE;
15979             if (target == currentMove + 1) {
15980                 if(moveList[target - 1][4] == ';') { // multi-leg
15981                     killX = moveList[target - 1][5] - AAA;
15982                     killY = moveList[target - 1][6] - ONE;
15983                 }
15984                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15985                 killX = killY = -1;
15986             }
15987             if (appData.highlightLastMove) {
15988                 SetHighlights(fromX, fromY, toX, toY);
15989             }
15990         }
15991     }
15992     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15993         gameMode == Training || gameMode == PlayFromGameFile ||
15994         gameMode == AnalyzeFile) {
15995         while (currentMove < target) {
15996             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15997             SendMoveToProgram(currentMove++, &first);
15998         }
15999     } else {
16000         currentMove = target;
16001     }
16002
16003     if (gameMode == EditGame || gameMode == EndOfGame) {
16004         whiteTimeRemaining = timeRemaining[0][currentMove];
16005         blackTimeRemaining = timeRemaining[1][currentMove];
16006     }
16007     DisplayBothClocks();
16008     DisplayMove(currentMove - 1);
16009     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16010     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16011     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16012         DisplayComment(currentMove - 1, commentList[currentMove]);
16013     }
16014     ClearMap(); // [HGM] exclude: invalidate map
16015 }
16016
16017
16018 void
16019 ForwardEvent ()
16020 {
16021     if (gameMode == IcsExamining && !pausing) {
16022         SendToICS(ics_prefix);
16023         SendToICS("forward\n");
16024     } else {
16025         ForwardInner(currentMove + 1);
16026     }
16027 }
16028
16029 void
16030 ToEndEvent ()
16031 {
16032     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16033         /* to optimze, we temporarily turn off analysis mode while we feed
16034          * the remaining moves to the engine. Otherwise we get analysis output
16035          * after each move.
16036          */
16037         if (first.analysisSupport) {
16038           SendToProgram("exit\nforce\n", &first);
16039           first.analyzing = FALSE;
16040         }
16041     }
16042
16043     if (gameMode == IcsExamining && !pausing) {
16044         SendToICS(ics_prefix);
16045         SendToICS("forward 999999\n");
16046     } else {
16047         ForwardInner(forwardMostMove);
16048     }
16049
16050     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16051         /* we have fed all the moves, so reactivate analysis mode */
16052         SendToProgram("analyze\n", &first);
16053         first.analyzing = TRUE;
16054         /*first.maybeThinking = TRUE;*/
16055         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16056     }
16057 }
16058
16059 void
16060 BackwardInner (int target)
16061 {
16062     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16063
16064     if (appData.debugMode)
16065         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16066                 target, currentMove, forwardMostMove);
16067
16068     if (gameMode == EditPosition) return;
16069     seekGraphUp = FALSE;
16070     MarkTargetSquares(1);
16071     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16072     if (currentMove <= backwardMostMove) {
16073         ClearHighlights();
16074         DrawPosition(full_redraw, boards[currentMove]);
16075         return;
16076     }
16077     if (gameMode == PlayFromGameFile && !pausing)
16078       PauseEvent();
16079
16080     if (moveList[target][0]) {
16081         int fromX, fromY, toX, toY;
16082         toX = moveList[target][2] - AAA;
16083         toY = moveList[target][3] - ONE;
16084         if (moveList[target][1] == '@') {
16085             if (appData.highlightLastMove) {
16086                 SetHighlights(-1, -1, toX, toY);
16087             }
16088         } else {
16089             fromX = moveList[target][0] - AAA;
16090             fromY = moveList[target][1] - ONE;
16091             if (target == currentMove - 1) {
16092                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16093             }
16094             if (appData.highlightLastMove) {
16095                 SetHighlights(fromX, fromY, toX, toY);
16096             }
16097         }
16098     }
16099     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16100         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16101         while (currentMove > target) {
16102             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16103                 // null move cannot be undone. Reload program with move history before it.
16104                 int i;
16105                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16106                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16107                 }
16108                 SendBoard(&first, i);
16109               if(second.analyzing) SendBoard(&second, i);
16110                 for(currentMove=i; currentMove<target; currentMove++) {
16111                     SendMoveToProgram(currentMove, &first);
16112                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16113                 }
16114                 break;
16115             }
16116             SendToBoth("undo\n");
16117             currentMove--;
16118         }
16119     } else {
16120         currentMove = target;
16121     }
16122
16123     if (gameMode == EditGame || gameMode == EndOfGame) {
16124         whiteTimeRemaining = timeRemaining[0][currentMove];
16125         blackTimeRemaining = timeRemaining[1][currentMove];
16126     }
16127     DisplayBothClocks();
16128     DisplayMove(currentMove - 1);
16129     DrawPosition(full_redraw, boards[currentMove]);
16130     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16131     // [HGM] PV info: routine tests if comment empty
16132     DisplayComment(currentMove - 1, commentList[currentMove]);
16133     ClearMap(); // [HGM] exclude: invalidate map
16134 }
16135
16136 void
16137 BackwardEvent ()
16138 {
16139     if (gameMode == IcsExamining && !pausing) {
16140         SendToICS(ics_prefix);
16141         SendToICS("backward\n");
16142     } else {
16143         BackwardInner(currentMove - 1);
16144     }
16145 }
16146
16147 void
16148 ToStartEvent ()
16149 {
16150     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16151         /* to optimize, we temporarily turn off analysis mode while we undo
16152          * all the moves. Otherwise we get analysis output after each undo.
16153          */
16154         if (first.analysisSupport) {
16155           SendToProgram("exit\nforce\n", &first);
16156           first.analyzing = FALSE;
16157         }
16158     }
16159
16160     if (gameMode == IcsExamining && !pausing) {
16161         SendToICS(ics_prefix);
16162         SendToICS("backward 999999\n");
16163     } else {
16164         BackwardInner(backwardMostMove);
16165     }
16166
16167     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16168         /* we have fed all the moves, so reactivate analysis mode */
16169         SendToProgram("analyze\n", &first);
16170         first.analyzing = TRUE;
16171         /*first.maybeThinking = TRUE;*/
16172         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16173     }
16174 }
16175
16176 void
16177 ToNrEvent (int to)
16178 {
16179   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16180   if (to >= forwardMostMove) to = forwardMostMove;
16181   if (to <= backwardMostMove) to = backwardMostMove;
16182   if (to < currentMove) {
16183     BackwardInner(to);
16184   } else {
16185     ForwardInner(to);
16186   }
16187 }
16188
16189 void
16190 RevertEvent (Boolean annotate)
16191 {
16192     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16193         return;
16194     }
16195     if (gameMode != IcsExamining) {
16196         DisplayError(_("You are not examining a game"), 0);
16197         return;
16198     }
16199     if (pausing) {
16200         DisplayError(_("You can't revert while pausing"), 0);
16201         return;
16202     }
16203     SendToICS(ics_prefix);
16204     SendToICS("revert\n");
16205 }
16206
16207 void
16208 RetractMoveEvent ()
16209 {
16210     switch (gameMode) {
16211       case MachinePlaysWhite:
16212       case MachinePlaysBlack:
16213         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16214             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16215             return;
16216         }
16217         if (forwardMostMove < 2) return;
16218         currentMove = forwardMostMove = forwardMostMove - 2;
16219         whiteTimeRemaining = timeRemaining[0][currentMove];
16220         blackTimeRemaining = timeRemaining[1][currentMove];
16221         DisplayBothClocks();
16222         DisplayMove(currentMove - 1);
16223         ClearHighlights();/*!! could figure this out*/
16224         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16225         SendToProgram("remove\n", &first);
16226         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16227         break;
16228
16229       case BeginningOfGame:
16230       default:
16231         break;
16232
16233       case IcsPlayingWhite:
16234       case IcsPlayingBlack:
16235         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16236             SendToICS(ics_prefix);
16237             SendToICS("takeback 2\n");
16238         } else {
16239             SendToICS(ics_prefix);
16240             SendToICS("takeback 1\n");
16241         }
16242         break;
16243     }
16244 }
16245
16246 void
16247 MoveNowEvent ()
16248 {
16249     ChessProgramState *cps;
16250
16251     switch (gameMode) {
16252       case MachinePlaysWhite:
16253         if (!WhiteOnMove(forwardMostMove)) {
16254             DisplayError(_("It is your turn"), 0);
16255             return;
16256         }
16257         cps = &first;
16258         break;
16259       case MachinePlaysBlack:
16260         if (WhiteOnMove(forwardMostMove)) {
16261             DisplayError(_("It is your turn"), 0);
16262             return;
16263         }
16264         cps = &first;
16265         break;
16266       case TwoMachinesPlay:
16267         if (WhiteOnMove(forwardMostMove) ==
16268             (first.twoMachinesColor[0] == 'w')) {
16269             cps = &first;
16270         } else {
16271             cps = &second;
16272         }
16273         break;
16274       case BeginningOfGame:
16275       default:
16276         return;
16277     }
16278     SendToProgram("?\n", cps);
16279 }
16280
16281 void
16282 TruncateGameEvent ()
16283 {
16284     EditGameEvent();
16285     if (gameMode != EditGame) return;
16286     TruncateGame();
16287 }
16288
16289 void
16290 TruncateGame ()
16291 {
16292     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16293     if (forwardMostMove > currentMove) {
16294         if (gameInfo.resultDetails != NULL) {
16295             free(gameInfo.resultDetails);
16296             gameInfo.resultDetails = NULL;
16297             gameInfo.result = GameUnfinished;
16298         }
16299         forwardMostMove = currentMove;
16300         HistorySet(parseList, backwardMostMove, forwardMostMove,
16301                    currentMove-1);
16302     }
16303 }
16304
16305 void
16306 HintEvent ()
16307 {
16308     if (appData.noChessProgram) return;
16309     switch (gameMode) {
16310       case MachinePlaysWhite:
16311         if (WhiteOnMove(forwardMostMove)) {
16312             DisplayError(_("Wait until your turn."), 0);
16313             return;
16314         }
16315         break;
16316       case BeginningOfGame:
16317       case MachinePlaysBlack:
16318         if (!WhiteOnMove(forwardMostMove)) {
16319             DisplayError(_("Wait until your turn."), 0);
16320             return;
16321         }
16322         break;
16323       default:
16324         DisplayError(_("No hint available"), 0);
16325         return;
16326     }
16327     SendToProgram("hint\n", &first);
16328     hintRequested = TRUE;
16329 }
16330
16331 int
16332 SaveSelected (FILE *g, int dummy, char *dummy2)
16333 {
16334     ListGame * lg = (ListGame *) gameList.head;
16335     int nItem, cnt=0;
16336     FILE *f;
16337
16338     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16339         DisplayError(_("Game list not loaded or empty"), 0);
16340         return 0;
16341     }
16342
16343     creatingBook = TRUE; // suppresses stuff during load game
16344
16345     /* Get list size */
16346     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16347         if(lg->position >= 0) { // selected?
16348             LoadGame(f, nItem, "", TRUE);
16349             SaveGamePGN2(g); // leaves g open
16350             cnt++; DoEvents();
16351         }
16352         lg = (ListGame *) lg->node.succ;
16353     }
16354
16355     fclose(g);
16356     creatingBook = FALSE;
16357
16358     return cnt;
16359 }
16360
16361 void
16362 CreateBookEvent ()
16363 {
16364     ListGame * lg = (ListGame *) gameList.head;
16365     FILE *f, *g;
16366     int nItem;
16367     static int secondTime = FALSE;
16368
16369     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16370         DisplayError(_("Game list not loaded or empty"), 0);
16371         return;
16372     }
16373
16374     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16375         fclose(g);
16376         secondTime++;
16377         DisplayNote(_("Book file exists! Try again for overwrite."));
16378         return;
16379     }
16380
16381     creatingBook = TRUE;
16382     secondTime = FALSE;
16383
16384     /* Get list size */
16385     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16386         if(lg->position >= 0) {
16387             LoadGame(f, nItem, "", TRUE);
16388             AddGameToBook(TRUE);
16389             DoEvents();
16390         }
16391         lg = (ListGame *) lg->node.succ;
16392     }
16393
16394     creatingBook = FALSE;
16395     FlushBook();
16396 }
16397
16398 void
16399 BookEvent ()
16400 {
16401     if (appData.noChessProgram) return;
16402     switch (gameMode) {
16403       case MachinePlaysWhite:
16404         if (WhiteOnMove(forwardMostMove)) {
16405             DisplayError(_("Wait until your turn."), 0);
16406             return;
16407         }
16408         break;
16409       case BeginningOfGame:
16410       case MachinePlaysBlack:
16411         if (!WhiteOnMove(forwardMostMove)) {
16412             DisplayError(_("Wait until your turn."), 0);
16413             return;
16414         }
16415         break;
16416       case EditPosition:
16417         EditPositionDone(TRUE);
16418         break;
16419       case TwoMachinesPlay:
16420         return;
16421       default:
16422         break;
16423     }
16424     SendToProgram("bk\n", &first);
16425     bookOutput[0] = NULLCHAR;
16426     bookRequested = TRUE;
16427 }
16428
16429 void
16430 AboutGameEvent ()
16431 {
16432     char *tags = PGNTags(&gameInfo);
16433     TagsPopUp(tags, CmailMsg());
16434     free(tags);
16435 }
16436
16437 /* end button procedures */
16438
16439 void
16440 PrintPosition (FILE *fp, int move)
16441 {
16442     int i, j;
16443
16444     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16445         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16446             char c = PieceToChar(boards[move][i][j]);
16447             fputc(c == '?' ? '.' : c, fp);
16448             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16449         }
16450     }
16451     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16452       fprintf(fp, "white to play\n");
16453     else
16454       fprintf(fp, "black to play\n");
16455 }
16456
16457 void
16458 PrintOpponents (FILE *fp)
16459 {
16460     if (gameInfo.white != NULL) {
16461         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16462     } else {
16463         fprintf(fp, "\n");
16464     }
16465 }
16466
16467 /* Find last component of program's own name, using some heuristics */
16468 void
16469 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16470 {
16471     char *p, *q, c;
16472     int local = (strcmp(host, "localhost") == 0);
16473     while (!local && (p = strchr(prog, ';')) != NULL) {
16474         p++;
16475         while (*p == ' ') p++;
16476         prog = p;
16477     }
16478     if (*prog == '"' || *prog == '\'') {
16479         q = strchr(prog + 1, *prog);
16480     } else {
16481         q = strchr(prog, ' ');
16482     }
16483     if (q == NULL) q = prog + strlen(prog);
16484     p = q;
16485     while (p >= prog && *p != '/' && *p != '\\') p--;
16486     p++;
16487     if(p == prog && *p == '"') p++;
16488     c = *q; *q = 0;
16489     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16490     memcpy(buf, p, q - p);
16491     buf[q - p] = NULLCHAR;
16492     if (!local) {
16493         strcat(buf, "@");
16494         strcat(buf, host);
16495     }
16496 }
16497
16498 char *
16499 TimeControlTagValue ()
16500 {
16501     char buf[MSG_SIZ];
16502     if (!appData.clockMode) {
16503       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16504     } else if (movesPerSession > 0) {
16505       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16506     } else if (timeIncrement == 0) {
16507       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16508     } else {
16509       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16510     }
16511     return StrSave(buf);
16512 }
16513
16514 void
16515 SetGameInfo ()
16516 {
16517     /* This routine is used only for certain modes */
16518     VariantClass v = gameInfo.variant;
16519     ChessMove r = GameUnfinished;
16520     char *p = NULL;
16521
16522     if(keepInfo) return;
16523
16524     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16525         r = gameInfo.result;
16526         p = gameInfo.resultDetails;
16527         gameInfo.resultDetails = NULL;
16528     }
16529     ClearGameInfo(&gameInfo);
16530     gameInfo.variant = v;
16531
16532     switch (gameMode) {
16533       case MachinePlaysWhite:
16534         gameInfo.event = StrSave( appData.pgnEventHeader );
16535         gameInfo.site = StrSave(HostName());
16536         gameInfo.date = PGNDate();
16537         gameInfo.round = StrSave("-");
16538         gameInfo.white = StrSave(first.tidy);
16539         gameInfo.black = StrSave(UserName());
16540         gameInfo.timeControl = TimeControlTagValue();
16541         break;
16542
16543       case MachinePlaysBlack:
16544         gameInfo.event = StrSave( appData.pgnEventHeader );
16545         gameInfo.site = StrSave(HostName());
16546         gameInfo.date = PGNDate();
16547         gameInfo.round = StrSave("-");
16548         gameInfo.white = StrSave(UserName());
16549         gameInfo.black = StrSave(first.tidy);
16550         gameInfo.timeControl = TimeControlTagValue();
16551         break;
16552
16553       case TwoMachinesPlay:
16554         gameInfo.event = StrSave( appData.pgnEventHeader );
16555         gameInfo.site = StrSave(HostName());
16556         gameInfo.date = PGNDate();
16557         if (roundNr > 0) {
16558             char buf[MSG_SIZ];
16559             snprintf(buf, MSG_SIZ, "%d", roundNr);
16560             gameInfo.round = StrSave(buf);
16561         } else {
16562             gameInfo.round = StrSave("-");
16563         }
16564         if (first.twoMachinesColor[0] == 'w') {
16565             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16566             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16567         } else {
16568             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16569             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16570         }
16571         gameInfo.timeControl = TimeControlTagValue();
16572         break;
16573
16574       case EditGame:
16575         gameInfo.event = StrSave("Edited game");
16576         gameInfo.site = StrSave(HostName());
16577         gameInfo.date = PGNDate();
16578         gameInfo.round = StrSave("-");
16579         gameInfo.white = StrSave("-");
16580         gameInfo.black = StrSave("-");
16581         gameInfo.result = r;
16582         gameInfo.resultDetails = p;
16583         break;
16584
16585       case EditPosition:
16586         gameInfo.event = StrSave("Edited position");
16587         gameInfo.site = StrSave(HostName());
16588         gameInfo.date = PGNDate();
16589         gameInfo.round = StrSave("-");
16590         gameInfo.white = StrSave("-");
16591         gameInfo.black = StrSave("-");
16592         break;
16593
16594       case IcsPlayingWhite:
16595       case IcsPlayingBlack:
16596       case IcsObserving:
16597       case IcsExamining:
16598         break;
16599
16600       case PlayFromGameFile:
16601         gameInfo.event = StrSave("Game from non-PGN file");
16602         gameInfo.site = StrSave(HostName());
16603         gameInfo.date = PGNDate();
16604         gameInfo.round = StrSave("-");
16605         gameInfo.white = StrSave("?");
16606         gameInfo.black = StrSave("?");
16607         break;
16608
16609       default:
16610         break;
16611     }
16612 }
16613
16614 void
16615 ReplaceComment (int index, char *text)
16616 {
16617     int len;
16618     char *p;
16619     float score;
16620
16621     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16622        pvInfoList[index-1].depth == len &&
16623        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16624        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16625     while (*text == '\n') text++;
16626     len = strlen(text);
16627     while (len > 0 && text[len - 1] == '\n') len--;
16628
16629     if (commentList[index] != NULL)
16630       free(commentList[index]);
16631
16632     if (len == 0) {
16633         commentList[index] = NULL;
16634         return;
16635     }
16636   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16637       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16638       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16639     commentList[index] = (char *) malloc(len + 2);
16640     strncpy(commentList[index], text, len);
16641     commentList[index][len] = '\n';
16642     commentList[index][len + 1] = NULLCHAR;
16643   } else {
16644     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16645     char *p;
16646     commentList[index] = (char *) malloc(len + 7);
16647     safeStrCpy(commentList[index], "{\n", 3);
16648     safeStrCpy(commentList[index]+2, text, len+1);
16649     commentList[index][len+2] = NULLCHAR;
16650     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16651     strcat(commentList[index], "\n}\n");
16652   }
16653 }
16654
16655 void
16656 CrushCRs (char *text)
16657 {
16658   char *p = text;
16659   char *q = text;
16660   char ch;
16661
16662   do {
16663     ch = *p++;
16664     if (ch == '\r') continue;
16665     *q++ = ch;
16666   } while (ch != '\0');
16667 }
16668
16669 void
16670 AppendComment (int index, char *text, Boolean addBraces)
16671 /* addBraces  tells if we should add {} */
16672 {
16673     int oldlen, len;
16674     char *old;
16675
16676 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16677     if(addBraces == 3) addBraces = 0; else // force appending literally
16678     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16679
16680     CrushCRs(text);
16681     while (*text == '\n') text++;
16682     len = strlen(text);
16683     while (len > 0 && text[len - 1] == '\n') len--;
16684     text[len] = NULLCHAR;
16685
16686     if (len == 0) return;
16687
16688     if (commentList[index] != NULL) {
16689       Boolean addClosingBrace = addBraces;
16690         old = commentList[index];
16691         oldlen = strlen(old);
16692         while(commentList[index][oldlen-1] ==  '\n')
16693           commentList[index][--oldlen] = NULLCHAR;
16694         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16695         safeStrCpy(commentList[index], old, oldlen + len + 6);
16696         free(old);
16697         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16698         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16699           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16700           while (*text == '\n') { text++; len--; }
16701           commentList[index][--oldlen] = NULLCHAR;
16702       }
16703         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16704         else          strcat(commentList[index], "\n");
16705         strcat(commentList[index], text);
16706         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16707         else          strcat(commentList[index], "\n");
16708     } else {
16709         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16710         if(addBraces)
16711           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16712         else commentList[index][0] = NULLCHAR;
16713         strcat(commentList[index], text);
16714         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16715         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16716     }
16717 }
16718
16719 static char *
16720 FindStr (char * text, char * sub_text)
16721 {
16722     char * result = strstr( text, sub_text );
16723
16724     if( result != NULL ) {
16725         result += strlen( sub_text );
16726     }
16727
16728     return result;
16729 }
16730
16731 /* [AS] Try to extract PV info from PGN comment */
16732 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16733 char *
16734 GetInfoFromComment (int index, char * text)
16735 {
16736     char * sep = text, *p;
16737
16738     if( text != NULL && index > 0 ) {
16739         int score = 0;
16740         int depth = 0;
16741         int time = -1, sec = 0, deci;
16742         char * s_eval = FindStr( text, "[%eval " );
16743         char * s_emt = FindStr( text, "[%emt " );
16744 #if 0
16745         if( s_eval != NULL || s_emt != NULL ) {
16746 #else
16747         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16748 #endif
16749             /* New style */
16750             char delim;
16751
16752             if( s_eval != NULL ) {
16753                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16754                     return text;
16755                 }
16756
16757                 if( delim != ']' ) {
16758                     return text;
16759                 }
16760             }
16761
16762             if( s_emt != NULL ) {
16763             }
16764                 return text;
16765         }
16766         else {
16767             /* We expect something like: [+|-]nnn.nn/dd */
16768             int score_lo = 0;
16769
16770             if(*text != '{') return text; // [HGM] braces: must be normal comment
16771
16772             sep = strchr( text, '/' );
16773             if( sep == NULL || sep < (text+4) ) {
16774                 return text;
16775             }
16776
16777             p = text;
16778             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16779             if(p[1] == '(') { // comment starts with PV
16780                p = strchr(p, ')'); // locate end of PV
16781                if(p == NULL || sep < p+5) return text;
16782                // at this point we have something like "{(.*) +0.23/6 ..."
16783                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16784                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16785                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16786             }
16787             time = -1; sec = -1; deci = -1;
16788             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16789                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16790                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16791                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16792                 return text;
16793             }
16794
16795             if( score_lo < 0 || score_lo >= 100 ) {
16796                 return text;
16797             }
16798
16799             if(sec >= 0) time = 600*time + 10*sec; else
16800             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16801
16802             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16803
16804             /* [HGM] PV time: now locate end of PV info */
16805             while( *++sep >= '0' && *sep <= '9'); // strip depth
16806             if(time >= 0)
16807             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16808             if(sec >= 0)
16809             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16810             if(deci >= 0)
16811             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16812             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16813         }
16814
16815         if( depth <= 0 ) {
16816             return text;
16817         }
16818
16819         if( time < 0 ) {
16820             time = -1;
16821         }
16822
16823         pvInfoList[index-1].depth = depth;
16824         pvInfoList[index-1].score = score;
16825         pvInfoList[index-1].time  = 10*time; // centi-sec
16826         if(*sep == '}') *sep = 0; else *--sep = '{';
16827         if(p != text) {
16828             while(*p++ = *sep++)
16829                                 ;
16830             sep = text;
16831         } // squeeze out space between PV and comment, and return both
16832     }
16833     return sep;
16834 }
16835
16836 void
16837 SendToProgram (char *message, ChessProgramState *cps)
16838 {
16839     int count, outCount, error;
16840     char buf[MSG_SIZ];
16841
16842     if (cps->pr == NoProc) return;
16843     Attention(cps);
16844
16845     if (appData.debugMode) {
16846         TimeMark now;
16847         GetTimeMark(&now);
16848         fprintf(debugFP, "%ld >%-6s: %s",
16849                 SubtractTimeMarks(&now, &programStartTime),
16850                 cps->which, message);
16851         if(serverFP)
16852             fprintf(serverFP, "%ld >%-6s: %s",
16853                 SubtractTimeMarks(&now, &programStartTime),
16854                 cps->which, message), fflush(serverFP);
16855     }
16856
16857     count = strlen(message);
16858     outCount = OutputToProcess(cps->pr, message, count, &error);
16859     if (outCount < count && !exiting
16860                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16861       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16862       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16863         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16864             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16865                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16866                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16867                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16868             } else {
16869                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16870                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16871                 gameInfo.result = res;
16872             }
16873             gameInfo.resultDetails = StrSave(buf);
16874         }
16875         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16876         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16877     }
16878 }
16879
16880 void
16881 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16882 {
16883     char *end_str;
16884     char buf[MSG_SIZ];
16885     ChessProgramState *cps = (ChessProgramState *)closure;
16886
16887     if (isr != cps->isr) return; /* Killed intentionally */
16888     if (count <= 0) {
16889         if (count == 0) {
16890             RemoveInputSource(cps->isr);
16891             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16892                     _(cps->which), cps->program);
16893             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16894             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16895                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16896                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16897                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16898                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16899                 } else {
16900                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16901                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16902                     gameInfo.result = res;
16903                 }
16904                 gameInfo.resultDetails = StrSave(buf);
16905             }
16906             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16907             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16908         } else {
16909             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16910                     _(cps->which), cps->program);
16911             RemoveInputSource(cps->isr);
16912
16913             /* [AS] Program is misbehaving badly... kill it */
16914             if( count == -2 ) {
16915                 DestroyChildProcess( cps->pr, 9 );
16916                 cps->pr = NoProc;
16917             }
16918
16919             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16920         }
16921         return;
16922     }
16923
16924     if ((end_str = strchr(message, '\r')) != NULL)
16925       *end_str = NULLCHAR;
16926     if ((end_str = strchr(message, '\n')) != NULL)
16927       *end_str = NULLCHAR;
16928
16929     if (appData.debugMode) {
16930         TimeMark now; int print = 1;
16931         char *quote = ""; char c; int i;
16932
16933         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16934                 char start = message[0];
16935                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16936                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16937                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16938                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16939                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16940                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16941                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16942                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16943                    sscanf(message, "hint: %c", &c)!=1 &&
16944                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16945                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16946                     print = (appData.engineComments >= 2);
16947                 }
16948                 message[0] = start; // restore original message
16949         }
16950         if(print) {
16951                 GetTimeMark(&now);
16952                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16953                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16954                         quote,
16955                         message);
16956                 if(serverFP)
16957                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16958                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16959                         quote,
16960                         message), fflush(serverFP);
16961         }
16962     }
16963
16964     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16965     if (appData.icsEngineAnalyze) {
16966         if (strstr(message, "whisper") != NULL ||
16967              strstr(message, "kibitz") != NULL ||
16968             strstr(message, "tellics") != NULL) return;
16969     }
16970
16971     HandleMachineMove(message, cps);
16972 }
16973
16974
16975 void
16976 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16977 {
16978     char buf[MSG_SIZ];
16979     int seconds;
16980
16981     if( timeControl_2 > 0 ) {
16982         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16983             tc = timeControl_2;
16984         }
16985     }
16986     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16987     inc /= cps->timeOdds;
16988     st  /= cps->timeOdds;
16989
16990     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16991
16992     if (st > 0) {
16993       /* Set exact time per move, normally using st command */
16994       if (cps->stKludge) {
16995         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16996         seconds = st % 60;
16997         if (seconds == 0) {
16998           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16999         } else {
17000           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17001         }
17002       } else {
17003         snprintf(buf, MSG_SIZ, "st %d\n", st);
17004       }
17005     } else {
17006       /* Set conventional or incremental time control, using level command */
17007       if (seconds == 0) {
17008         /* Note old gnuchess bug -- minutes:seconds used to not work.
17009            Fixed in later versions, but still avoid :seconds
17010            when seconds is 0. */
17011         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17012       } else {
17013         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17014                  seconds, inc/1000.);
17015       }
17016     }
17017     SendToProgram(buf, cps);
17018
17019     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17020     /* Orthogonally, limit search to given depth */
17021     if (sd > 0) {
17022       if (cps->sdKludge) {
17023         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17024       } else {
17025         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17026       }
17027       SendToProgram(buf, cps);
17028     }
17029
17030     if(cps->nps >= 0) { /* [HGM] nps */
17031         if(cps->supportsNPS == FALSE)
17032           cps->nps = -1; // don't use if engine explicitly says not supported!
17033         else {
17034           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17035           SendToProgram(buf, cps);
17036         }
17037     }
17038 }
17039
17040 ChessProgramState *
17041 WhitePlayer ()
17042 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17043 {
17044     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17045        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17046         return &second;
17047     return &first;
17048 }
17049
17050 void
17051 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17052 {
17053     char message[MSG_SIZ];
17054     long time, otime;
17055
17056     /* Note: this routine must be called when the clocks are stopped
17057        or when they have *just* been set or switched; otherwise
17058        it will be off by the time since the current tick started.
17059     */
17060     if (machineWhite) {
17061         time = whiteTimeRemaining / 10;
17062         otime = blackTimeRemaining / 10;
17063     } else {
17064         time = blackTimeRemaining / 10;
17065         otime = whiteTimeRemaining / 10;
17066     }
17067     /* [HGM] translate opponent's time by time-odds factor */
17068     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17069
17070     if (time <= 0) time = 1;
17071     if (otime <= 0) otime = 1;
17072
17073     snprintf(message, MSG_SIZ, "time %ld\n", time);
17074     SendToProgram(message, cps);
17075
17076     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17077     SendToProgram(message, cps);
17078 }
17079
17080 char *
17081 EngineDefinedVariant (ChessProgramState *cps, int n)
17082 {   // return name of n-th unknown variant that engine supports
17083     static char buf[MSG_SIZ];
17084     char *p, *s = cps->variants;
17085     if(!s) return NULL;
17086     do { // parse string from variants feature
17087       VariantClass v;
17088         p = strchr(s, ',');
17089         if(p) *p = NULLCHAR;
17090       v = StringToVariant(s);
17091       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17092         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17093             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17094                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17095                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17096                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17097             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17098         }
17099         if(p) *p++ = ',';
17100         if(n < 0) return buf;
17101     } while(s = p);
17102     return NULL;
17103 }
17104
17105 int
17106 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17107 {
17108   char buf[MSG_SIZ];
17109   int len = strlen(name);
17110   int val;
17111
17112   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17113     (*p) += len + 1;
17114     sscanf(*p, "%d", &val);
17115     *loc = (val != 0);
17116     while (**p && **p != ' ')
17117       (*p)++;
17118     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17119     SendToProgram(buf, cps);
17120     return TRUE;
17121   }
17122   return FALSE;
17123 }
17124
17125 int
17126 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17127 {
17128   char buf[MSG_SIZ];
17129   int len = strlen(name);
17130   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17131     (*p) += len + 1;
17132     sscanf(*p, "%d", loc);
17133     while (**p && **p != ' ') (*p)++;
17134     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17135     SendToProgram(buf, cps);
17136     return TRUE;
17137   }
17138   return FALSE;
17139 }
17140
17141 int
17142 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17143 {
17144   char buf[MSG_SIZ];
17145   int len = strlen(name);
17146   if (strncmp((*p), name, len) == 0
17147       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17148     (*p) += len + 2;
17149     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17150     sscanf(*p, "%[^\"]", *loc);
17151     while (**p && **p != '\"') (*p)++;
17152     if (**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 ParseOption (Option *opt, ChessProgramState *cps)
17162 // [HGM] options: process the string that defines an engine option, and determine
17163 // name, type, default value, and allowed value range
17164 {
17165         char *p, *q, buf[MSG_SIZ];
17166         int n, min = (-1)<<31, max = 1<<31, def;
17167
17168         opt->target = &opt->value;   // OK for spin/slider and checkbox
17169         if(p = strstr(opt->name, " -spin ")) {
17170             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17171             if(max < min) max = min; // enforce consistency
17172             if(def < min) def = min;
17173             if(def > max) def = max;
17174             opt->value = def;
17175             opt->min = min;
17176             opt->max = max;
17177             opt->type = Spin;
17178         } else if((p = strstr(opt->name, " -slider "))) {
17179             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17180             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17181             if(max < min) max = min; // enforce consistency
17182             if(def < min) def = min;
17183             if(def > max) def = max;
17184             opt->value = def;
17185             opt->min = min;
17186             opt->max = max;
17187             opt->type = Spin; // Slider;
17188         } else if((p = strstr(opt->name, " -string "))) {
17189             opt->textValue = p+9;
17190             opt->type = TextBox;
17191             opt->target = &opt->textValue;
17192         } else if((p = strstr(opt->name, " -file "))) {
17193             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17194             opt->target = opt->textValue = p+7;
17195             opt->type = FileName; // FileName;
17196             opt->target = &opt->textValue;
17197         } else if((p = strstr(opt->name, " -path "))) {
17198             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17199             opt->target = opt->textValue = p+7;
17200             opt->type = PathName; // PathName;
17201             opt->target = &opt->textValue;
17202         } else if(p = strstr(opt->name, " -check ")) {
17203             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17204             opt->value = (def != 0);
17205             opt->type = CheckBox;
17206         } else if(p = strstr(opt->name, " -combo ")) {
17207             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17208             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17209             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17210             opt->value = n = 0;
17211             while(q = StrStr(q, " /// ")) {
17212                 n++; *q = 0;    // count choices, and null-terminate each of them
17213                 q += 5;
17214                 if(*q == '*') { // remember default, which is marked with * prefix
17215                     q++;
17216                     opt->value = n;
17217                 }
17218                 cps->comboList[cps->comboCnt++] = q;
17219             }
17220             cps->comboList[cps->comboCnt++] = NULL;
17221             opt->max = n + 1;
17222             opt->type = ComboBox;
17223         } else if(p = strstr(opt->name, " -button")) {
17224             opt->type = Button;
17225         } else if(p = strstr(opt->name, " -save")) {
17226             opt->type = SaveButton;
17227         } else return FALSE;
17228         *p = 0; // terminate option name
17229         // now look if the command-line options define a setting for this engine option.
17230         if(cps->optionSettings && cps->optionSettings[0])
17231             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17232         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17233           snprintf(buf, MSG_SIZ, "option %s", p);
17234                 if(p = strstr(buf, ",")) *p = 0;
17235                 if(q = strchr(buf, '=')) switch(opt->type) {
17236                     case ComboBox:
17237                         for(n=0; n<opt->max; n++)
17238                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17239                         break;
17240                     case TextBox:
17241                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17242                         break;
17243                     case Spin:
17244                     case CheckBox:
17245                         opt->value = atoi(q+1);
17246                     default:
17247                         break;
17248                 }
17249                 strcat(buf, "\n");
17250                 SendToProgram(buf, cps);
17251         }
17252         return TRUE;
17253 }
17254
17255 void
17256 FeatureDone (ChessProgramState *cps, int val)
17257 {
17258   DelayedEventCallback cb = GetDelayedEvent();
17259   if ((cb == InitBackEnd3 && cps == &first) ||
17260       (cb == SettingsMenuIfReady && cps == &second) ||
17261       (cb == LoadEngine) ||
17262       (cb == TwoMachinesEventIfReady)) {
17263     CancelDelayedEvent();
17264     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17265   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17266   cps->initDone = val;
17267   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17268 }
17269
17270 /* Parse feature command from engine */
17271 void
17272 ParseFeatures (char *args, ChessProgramState *cps)
17273 {
17274   char *p = args;
17275   char *q = NULL;
17276   int val;
17277   char buf[MSG_SIZ];
17278
17279   for (;;) {
17280     while (*p == ' ') p++;
17281     if (*p == NULLCHAR) return;
17282
17283     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17284     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17285     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17286     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17287     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17288     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17289     if (BoolFeature(&p, "reuse", &val, cps)) {
17290       /* Engine can disable reuse, but can't enable it if user said no */
17291       if (!val) cps->reuse = FALSE;
17292       continue;
17293     }
17294     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17295     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17296       if (gameMode == TwoMachinesPlay) {
17297         DisplayTwoMachinesTitle();
17298       } else {
17299         DisplayTitle("");
17300       }
17301       continue;
17302     }
17303     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17304     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17305     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17306     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17307     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17308     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17309     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17310     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17311     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17312     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17313     if (IntFeature(&p, "done", &val, cps)) {
17314       FeatureDone(cps, val);
17315       continue;
17316     }
17317     /* Added by Tord: */
17318     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17319     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17320     /* End of additions by Tord */
17321
17322     /* [HGM] added features: */
17323     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17324     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17325     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17326     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17327     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17328     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17329     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17330     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17331         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17332         FREE(cps->option[cps->nrOptions].name);
17333         cps->option[cps->nrOptions].name = q; q = NULL;
17334         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17335           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17336             SendToProgram(buf, cps);
17337             continue;
17338         }
17339         if(cps->nrOptions >= MAX_OPTIONS) {
17340             cps->nrOptions--;
17341             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17342             DisplayError(buf, 0);
17343         }
17344         continue;
17345     }
17346     /* End of additions by HGM */
17347
17348     /* unknown feature: complain and skip */
17349     q = p;
17350     while (*q && *q != '=') q++;
17351     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17352     SendToProgram(buf, cps);
17353     p = q;
17354     if (*p == '=') {
17355       p++;
17356       if (*p == '\"') {
17357         p++;
17358         while (*p && *p != '\"') p++;
17359         if (*p == '\"') p++;
17360       } else {
17361         while (*p && *p != ' ') p++;
17362       }
17363     }
17364   }
17365
17366 }
17367
17368 void
17369 PeriodicUpdatesEvent (int newState)
17370 {
17371     if (newState == appData.periodicUpdates)
17372       return;
17373
17374     appData.periodicUpdates=newState;
17375
17376     /* Display type changes, so update it now */
17377 //    DisplayAnalysis();
17378
17379     /* Get the ball rolling again... */
17380     if (newState) {
17381         AnalysisPeriodicEvent(1);
17382         StartAnalysisClock();
17383     }
17384 }
17385
17386 void
17387 PonderNextMoveEvent (int newState)
17388 {
17389     if (newState == appData.ponderNextMove) return;
17390     if (gameMode == EditPosition) EditPositionDone(TRUE);
17391     if (newState) {
17392         SendToProgram("hard\n", &first);
17393         if (gameMode == TwoMachinesPlay) {
17394             SendToProgram("hard\n", &second);
17395         }
17396     } else {
17397         SendToProgram("easy\n", &first);
17398         thinkOutput[0] = NULLCHAR;
17399         if (gameMode == TwoMachinesPlay) {
17400             SendToProgram("easy\n", &second);
17401         }
17402     }
17403     appData.ponderNextMove = newState;
17404 }
17405
17406 void
17407 NewSettingEvent (int option, int *feature, char *command, int value)
17408 {
17409     char buf[MSG_SIZ];
17410
17411     if (gameMode == EditPosition) EditPositionDone(TRUE);
17412     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17413     if(feature == NULL || *feature) SendToProgram(buf, &first);
17414     if (gameMode == TwoMachinesPlay) {
17415         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17416     }
17417 }
17418
17419 void
17420 ShowThinkingEvent ()
17421 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17422 {
17423     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17424     int newState = appData.showThinking
17425         // [HGM] thinking: other features now need thinking output as well
17426         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17427
17428     if (oldState == newState) return;
17429     oldState = newState;
17430     if (gameMode == EditPosition) EditPositionDone(TRUE);
17431     if (oldState) {
17432         SendToProgram("post\n", &first);
17433         if (gameMode == TwoMachinesPlay) {
17434             SendToProgram("post\n", &second);
17435         }
17436     } else {
17437         SendToProgram("nopost\n", &first);
17438         thinkOutput[0] = NULLCHAR;
17439         if (gameMode == TwoMachinesPlay) {
17440             SendToProgram("nopost\n", &second);
17441         }
17442     }
17443 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17444 }
17445
17446 void
17447 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17448 {
17449   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17450   if (pr == NoProc) return;
17451   AskQuestion(title, question, replyPrefix, pr);
17452 }
17453
17454 void
17455 TypeInEvent (char firstChar)
17456 {
17457     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17458         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17459         gameMode == AnalyzeMode || gameMode == EditGame ||
17460         gameMode == EditPosition || gameMode == IcsExamining ||
17461         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17462         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17463                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17464                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17465         gameMode == Training) PopUpMoveDialog(firstChar);
17466 }
17467
17468 void
17469 TypeInDoneEvent (char *move)
17470 {
17471         Board board;
17472         int n, fromX, fromY, toX, toY;
17473         char promoChar;
17474         ChessMove moveType;
17475
17476         // [HGM] FENedit
17477         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17478                 EditPositionPasteFEN(move);
17479                 return;
17480         }
17481         // [HGM] movenum: allow move number to be typed in any mode
17482         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17483           ToNrEvent(2*n-1);
17484           return;
17485         }
17486         // undocumented kludge: allow command-line option to be typed in!
17487         // (potentially fatal, and does not implement the effect of the option.)
17488         // should only be used for options that are values on which future decisions will be made,
17489         // and definitely not on options that would be used during initialization.
17490         if(strstr(move, "!!! -") == move) {
17491             ParseArgsFromString(move+4);
17492             return;
17493         }
17494
17495       if (gameMode != EditGame && currentMove != forwardMostMove &&
17496         gameMode != Training) {
17497         DisplayMoveError(_("Displayed move is not current"));
17498       } else {
17499         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17500           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17501         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17502         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17503           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17504           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17505         } else {
17506           DisplayMoveError(_("Could not parse move"));
17507         }
17508       }
17509 }
17510
17511 void
17512 DisplayMove (int moveNumber)
17513 {
17514     char message[MSG_SIZ];
17515     char res[MSG_SIZ];
17516     char cpThinkOutput[MSG_SIZ];
17517
17518     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17519
17520     if (moveNumber == forwardMostMove - 1 ||
17521         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17522
17523         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17524
17525         if (strchr(cpThinkOutput, '\n')) {
17526             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17527         }
17528     } else {
17529         *cpThinkOutput = NULLCHAR;
17530     }
17531
17532     /* [AS] Hide thinking from human user */
17533     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17534         *cpThinkOutput = NULLCHAR;
17535         if( thinkOutput[0] != NULLCHAR ) {
17536             int i;
17537
17538             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17539                 cpThinkOutput[i] = '.';
17540             }
17541             cpThinkOutput[i] = NULLCHAR;
17542             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17543         }
17544     }
17545
17546     if (moveNumber == forwardMostMove - 1 &&
17547         gameInfo.resultDetails != NULL) {
17548         if (gameInfo.resultDetails[0] == NULLCHAR) {
17549           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17550         } else {
17551           snprintf(res, MSG_SIZ, " {%s} %s",
17552                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17553         }
17554     } else {
17555         res[0] = NULLCHAR;
17556     }
17557
17558     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17559         DisplayMessage(res, cpThinkOutput);
17560     } else {
17561       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17562                 WhiteOnMove(moveNumber) ? " " : ".. ",
17563                 parseList[moveNumber], res);
17564         DisplayMessage(message, cpThinkOutput);
17565     }
17566 }
17567
17568 void
17569 DisplayComment (int moveNumber, char *text)
17570 {
17571     char title[MSG_SIZ];
17572
17573     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17574       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17575     } else {
17576       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17577               WhiteOnMove(moveNumber) ? " " : ".. ",
17578               parseList[moveNumber]);
17579     }
17580     if (text != NULL && (appData.autoDisplayComment || commentUp))
17581         CommentPopUp(title, text);
17582 }
17583
17584 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17585  * might be busy thinking or pondering.  It can be omitted if your
17586  * gnuchess is configured to stop thinking immediately on any user
17587  * input.  However, that gnuchess feature depends on the FIONREAD
17588  * ioctl, which does not work properly on some flavors of Unix.
17589  */
17590 void
17591 Attention (ChessProgramState *cps)
17592 {
17593 #if ATTENTION
17594     if (!cps->useSigint) return;
17595     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17596     switch (gameMode) {
17597       case MachinePlaysWhite:
17598       case MachinePlaysBlack:
17599       case TwoMachinesPlay:
17600       case IcsPlayingWhite:
17601       case IcsPlayingBlack:
17602       case AnalyzeMode:
17603       case AnalyzeFile:
17604         /* Skip if we know it isn't thinking */
17605         if (!cps->maybeThinking) return;
17606         if (appData.debugMode)
17607           fprintf(debugFP, "Interrupting %s\n", cps->which);
17608         InterruptChildProcess(cps->pr);
17609         cps->maybeThinking = FALSE;
17610         break;
17611       default:
17612         break;
17613     }
17614 #endif /*ATTENTION*/
17615 }
17616
17617 int
17618 CheckFlags ()
17619 {
17620     if (whiteTimeRemaining <= 0) {
17621         if (!whiteFlag) {
17622             whiteFlag = TRUE;
17623             if (appData.icsActive) {
17624                 if (appData.autoCallFlag &&
17625                     gameMode == IcsPlayingBlack && !blackFlag) {
17626                   SendToICS(ics_prefix);
17627                   SendToICS("flag\n");
17628                 }
17629             } else {
17630                 if (blackFlag) {
17631                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17632                 } else {
17633                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17634                     if (appData.autoCallFlag) {
17635                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17636                         return TRUE;
17637                     }
17638                 }
17639             }
17640         }
17641     }
17642     if (blackTimeRemaining <= 0) {
17643         if (!blackFlag) {
17644             blackFlag = TRUE;
17645             if (appData.icsActive) {
17646                 if (appData.autoCallFlag &&
17647                     gameMode == IcsPlayingWhite && !whiteFlag) {
17648                   SendToICS(ics_prefix);
17649                   SendToICS("flag\n");
17650                 }
17651             } else {
17652                 if (whiteFlag) {
17653                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17654                 } else {
17655                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17656                     if (appData.autoCallFlag) {
17657                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17658                         return TRUE;
17659                     }
17660                 }
17661             }
17662         }
17663     }
17664     return FALSE;
17665 }
17666
17667 void
17668 CheckTimeControl ()
17669 {
17670     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17671         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17672
17673     /*
17674      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17675      */
17676     if ( !WhiteOnMove(forwardMostMove) ) {
17677         /* White made time control */
17678         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17679         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17680         /* [HGM] time odds: correct new time quota for time odds! */
17681                                             / WhitePlayer()->timeOdds;
17682         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17683     } else {
17684         lastBlack -= blackTimeRemaining;
17685         /* Black made time control */
17686         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17687                                             / WhitePlayer()->other->timeOdds;
17688         lastWhite = whiteTimeRemaining;
17689     }
17690 }
17691
17692 void
17693 DisplayBothClocks ()
17694 {
17695     int wom = gameMode == EditPosition ?
17696       !blackPlaysFirst : WhiteOnMove(currentMove);
17697     DisplayWhiteClock(whiteTimeRemaining, wom);
17698     DisplayBlackClock(blackTimeRemaining, !wom);
17699 }
17700
17701
17702 /* Timekeeping seems to be a portability nightmare.  I think everyone
17703    has ftime(), but I'm really not sure, so I'm including some ifdefs
17704    to use other calls if you don't.  Clocks will be less accurate if
17705    you have neither ftime nor gettimeofday.
17706 */
17707
17708 /* VS 2008 requires the #include outside of the function */
17709 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17710 #include <sys/timeb.h>
17711 #endif
17712
17713 /* Get the current time as a TimeMark */
17714 void
17715 GetTimeMark (TimeMark *tm)
17716 {
17717 #if HAVE_GETTIMEOFDAY
17718
17719     struct timeval timeVal;
17720     struct timezone timeZone;
17721
17722     gettimeofday(&timeVal, &timeZone);
17723     tm->sec = (long) timeVal.tv_sec;
17724     tm->ms = (int) (timeVal.tv_usec / 1000L);
17725
17726 #else /*!HAVE_GETTIMEOFDAY*/
17727 #if HAVE_FTIME
17728
17729 // include <sys/timeb.h> / moved to just above start of function
17730     struct timeb timeB;
17731
17732     ftime(&timeB);
17733     tm->sec = (long) timeB.time;
17734     tm->ms = (int) timeB.millitm;
17735
17736 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17737     tm->sec = (long) time(NULL);
17738     tm->ms = 0;
17739 #endif
17740 #endif
17741 }
17742
17743 /* Return the difference in milliseconds between two
17744    time marks.  We assume the difference will fit in a long!
17745 */
17746 long
17747 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17748 {
17749     return 1000L*(tm2->sec - tm1->sec) +
17750            (long) (tm2->ms - tm1->ms);
17751 }
17752
17753
17754 /*
17755  * Code to manage the game clocks.
17756  *
17757  * In tournament play, black starts the clock and then white makes a move.
17758  * We give the human user a slight advantage if he is playing white---the
17759  * clocks don't run until he makes his first move, so it takes zero time.
17760  * Also, we don't account for network lag, so we could get out of sync
17761  * with GNU Chess's clock -- but then, referees are always right.
17762  */
17763
17764 static TimeMark tickStartTM;
17765 static long intendedTickLength;
17766
17767 long
17768 NextTickLength (long timeRemaining)
17769 {
17770     long nominalTickLength, nextTickLength;
17771
17772     if (timeRemaining > 0L && timeRemaining <= 10000L)
17773       nominalTickLength = 100L;
17774     else
17775       nominalTickLength = 1000L;
17776     nextTickLength = timeRemaining % nominalTickLength;
17777     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17778
17779     return nextTickLength;
17780 }
17781
17782 /* Adjust clock one minute up or down */
17783 void
17784 AdjustClock (Boolean which, int dir)
17785 {
17786     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17787     if(which) blackTimeRemaining += 60000*dir;
17788     else      whiteTimeRemaining += 60000*dir;
17789     DisplayBothClocks();
17790     adjustedClock = TRUE;
17791 }
17792
17793 /* Stop clocks and reset to a fresh time control */
17794 void
17795 ResetClocks ()
17796 {
17797     (void) StopClockTimer();
17798     if (appData.icsActive) {
17799         whiteTimeRemaining = blackTimeRemaining = 0;
17800     } else if (searchTime) {
17801         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17802         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17803     } else { /* [HGM] correct new time quote for time odds */
17804         whiteTC = blackTC = fullTimeControlString;
17805         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17806         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17807     }
17808     if (whiteFlag || blackFlag) {
17809         DisplayTitle("");
17810         whiteFlag = blackFlag = FALSE;
17811     }
17812     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17813     DisplayBothClocks();
17814     adjustedClock = FALSE;
17815 }
17816
17817 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17818
17819 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17820
17821 /* Decrement running clock by amount of time that has passed */
17822 void
17823 DecrementClocks ()
17824 {
17825     long tRemaining;
17826     long lastTickLength, fudge;
17827     TimeMark now;
17828
17829     if (!appData.clockMode) return;
17830     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17831
17832     GetTimeMark(&now);
17833
17834     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17835
17836     /* Fudge if we woke up a little too soon */
17837     fudge = intendedTickLength - lastTickLength;
17838     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17839
17840     if (WhiteOnMove(forwardMostMove)) {
17841         if(whiteNPS >= 0) lastTickLength = 0;
17842          tRemaining = whiteTimeRemaining -= lastTickLength;
17843         if( tRemaining < 0 && !appData.icsActive) {
17844             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17845             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17846                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17847                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17848             }
17849         }
17850         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17851         DisplayWhiteClock(whiteTimeRemaining - fudge,
17852                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17853         timeSuffix = 0;
17854     } else {
17855         if(blackNPS >= 0) lastTickLength = 0;
17856          tRemaining = blackTimeRemaining -= lastTickLength;
17857         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17858             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17859             if(suddenDeath) {
17860                 blackStartMove = forwardMostMove;
17861                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17862             }
17863         }
17864         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17865         DisplayBlackClock(blackTimeRemaining - fudge,
17866                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17867         timeSuffix = 0;
17868     }
17869     if (CheckFlags()) return;
17870
17871     if(twoBoards) { // count down secondary board's clocks as well
17872         activePartnerTime -= lastTickLength;
17873         partnerUp = 1;
17874         if(activePartner == 'W')
17875             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17876         else
17877             DisplayBlackClock(activePartnerTime, TRUE);
17878         partnerUp = 0;
17879     }
17880
17881     tickStartTM = now;
17882     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17883     StartClockTimer(intendedTickLength);
17884
17885     /* if the time remaining has fallen below the alarm threshold, sound the
17886      * alarm. if the alarm has sounded and (due to a takeback or time control
17887      * with increment) the time remaining has increased to a level above the
17888      * threshold, reset the alarm so it can sound again.
17889      */
17890
17891     if (appData.icsActive && appData.icsAlarm) {
17892
17893         /* make sure we are dealing with the user's clock */
17894         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17895                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17896            )) return;
17897
17898         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17899             alarmSounded = FALSE;
17900         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17901             PlayAlarmSound();
17902             alarmSounded = TRUE;
17903         }
17904     }
17905 }
17906
17907
17908 /* A player has just moved, so stop the previously running
17909    clock and (if in clock mode) start the other one.
17910    We redisplay both clocks in case we're in ICS mode, because
17911    ICS gives us an update to both clocks after every move.
17912    Note that this routine is called *after* forwardMostMove
17913    is updated, so the last fractional tick must be subtracted
17914    from the color that is *not* on move now.
17915 */
17916 void
17917 SwitchClocks (int newMoveNr)
17918 {
17919     long lastTickLength;
17920     TimeMark now;
17921     int flagged = FALSE;
17922
17923     GetTimeMark(&now);
17924
17925     if (StopClockTimer() && appData.clockMode) {
17926         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17927         if (!WhiteOnMove(forwardMostMove)) {
17928             if(blackNPS >= 0) lastTickLength = 0;
17929             blackTimeRemaining -= lastTickLength;
17930            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17931 //         if(pvInfoList[forwardMostMove].time == -1)
17932                  pvInfoList[forwardMostMove].time =               // use GUI time
17933                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17934         } else {
17935            if(whiteNPS >= 0) lastTickLength = 0;
17936            whiteTimeRemaining -= lastTickLength;
17937            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17938 //         if(pvInfoList[forwardMostMove].time == -1)
17939                  pvInfoList[forwardMostMove].time =
17940                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17941         }
17942         flagged = CheckFlags();
17943     }
17944     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17945     CheckTimeControl();
17946
17947     if (flagged || !appData.clockMode) return;
17948
17949     switch (gameMode) {
17950       case MachinePlaysBlack:
17951       case MachinePlaysWhite:
17952       case BeginningOfGame:
17953         if (pausing) return;
17954         break;
17955
17956       case EditGame:
17957       case PlayFromGameFile:
17958       case IcsExamining:
17959         return;
17960
17961       default:
17962         break;
17963     }
17964
17965     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17966         if(WhiteOnMove(forwardMostMove))
17967              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17968         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17969     }
17970
17971     tickStartTM = now;
17972     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17973       whiteTimeRemaining : blackTimeRemaining);
17974     StartClockTimer(intendedTickLength);
17975 }
17976
17977
17978 /* Stop both clocks */
17979 void
17980 StopClocks ()
17981 {
17982     long lastTickLength;
17983     TimeMark now;
17984
17985     if (!StopClockTimer()) return;
17986     if (!appData.clockMode) return;
17987
17988     GetTimeMark(&now);
17989
17990     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17991     if (WhiteOnMove(forwardMostMove)) {
17992         if(whiteNPS >= 0) lastTickLength = 0;
17993         whiteTimeRemaining -= lastTickLength;
17994         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17995     } else {
17996         if(blackNPS >= 0) lastTickLength = 0;
17997         blackTimeRemaining -= lastTickLength;
17998         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17999     }
18000     CheckFlags();
18001 }
18002
18003 /* Start clock of player on move.  Time may have been reset, so
18004    if clock is already running, stop and restart it. */
18005 void
18006 StartClocks ()
18007 {
18008     (void) StopClockTimer(); /* in case it was running already */
18009     DisplayBothClocks();
18010     if (CheckFlags()) return;
18011
18012     if (!appData.clockMode) return;
18013     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18014
18015     GetTimeMark(&tickStartTM);
18016     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18017       whiteTimeRemaining : blackTimeRemaining);
18018
18019    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18020     whiteNPS = blackNPS = -1;
18021     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18022        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18023         whiteNPS = first.nps;
18024     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18025        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18026         blackNPS = first.nps;
18027     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18028         whiteNPS = second.nps;
18029     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18030         blackNPS = second.nps;
18031     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18032
18033     StartClockTimer(intendedTickLength);
18034 }
18035
18036 char *
18037 TimeString (long ms)
18038 {
18039     long second, minute, hour, day;
18040     char *sign = "";
18041     static char buf[40], moveTime[8];
18042
18043     if (ms > 0 && ms <= 9900) {
18044       /* convert milliseconds to tenths, rounding up */
18045       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18046
18047       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18048       return buf;
18049     }
18050
18051     /* convert milliseconds to seconds, rounding up */
18052     /* use floating point to avoid strangeness of integer division
18053        with negative dividends on many machines */
18054     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18055
18056     if (second < 0) {
18057         sign = "-";
18058         second = -second;
18059     }
18060
18061     day = second / (60 * 60 * 24);
18062     second = second % (60 * 60 * 24);
18063     hour = second / (60 * 60);
18064     second = second % (60 * 60);
18065     minute = second / 60;
18066     second = second % 60;
18067
18068     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18069     else *moveTime = NULLCHAR;
18070
18071     if (day > 0)
18072       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18073               sign, day, hour, minute, second, moveTime);
18074     else if (hour > 0)
18075       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18076     else
18077       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18078
18079     return buf;
18080 }
18081
18082
18083 /*
18084  * This is necessary because some C libraries aren't ANSI C compliant yet.
18085  */
18086 char *
18087 StrStr (char *string, char *match)
18088 {
18089     int i, length;
18090
18091     length = strlen(match);
18092
18093     for (i = strlen(string) - length; i >= 0; i--, string++)
18094       if (!strncmp(match, string, length))
18095         return string;
18096
18097     return NULL;
18098 }
18099
18100 char *
18101 StrCaseStr (char *string, char *match)
18102 {
18103     int i, j, length;
18104
18105     length = strlen(match);
18106
18107     for (i = strlen(string) - length; i >= 0; i--, string++) {
18108         for (j = 0; j < length; j++) {
18109             if (ToLower(match[j]) != ToLower(string[j]))
18110               break;
18111         }
18112         if (j == length) return string;
18113     }
18114
18115     return NULL;
18116 }
18117
18118 #ifndef _amigados
18119 int
18120 StrCaseCmp (char *s1, char *s2)
18121 {
18122     char c1, c2;
18123
18124     for (;;) {
18125         c1 = ToLower(*s1++);
18126         c2 = ToLower(*s2++);
18127         if (c1 > c2) return 1;
18128         if (c1 < c2) return -1;
18129         if (c1 == NULLCHAR) return 0;
18130     }
18131 }
18132
18133
18134 int
18135 ToLower (int c)
18136 {
18137     return isupper(c) ? tolower(c) : c;
18138 }
18139
18140
18141 int
18142 ToUpper (int c)
18143 {
18144     return islower(c) ? toupper(c) : c;
18145 }
18146 #endif /* !_amigados    */
18147
18148 char *
18149 StrSave (char *s)
18150 {
18151   char *ret;
18152
18153   if ((ret = (char *) malloc(strlen(s) + 1)))
18154     {
18155       safeStrCpy(ret, s, strlen(s)+1);
18156     }
18157   return ret;
18158 }
18159
18160 char *
18161 StrSavePtr (char *s, char **savePtr)
18162 {
18163     if (*savePtr) {
18164         free(*savePtr);
18165     }
18166     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18167       safeStrCpy(*savePtr, s, strlen(s)+1);
18168     }
18169     return(*savePtr);
18170 }
18171
18172 char *
18173 PGNDate ()
18174 {
18175     time_t clock;
18176     struct tm *tm;
18177     char buf[MSG_SIZ];
18178
18179     clock = time((time_t *)NULL);
18180     tm = localtime(&clock);
18181     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18182             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18183     return StrSave(buf);
18184 }
18185
18186
18187 char *
18188 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18189 {
18190     int i, j, fromX, fromY, toX, toY;
18191     int whiteToPlay, haveRights = nrCastlingRights;
18192     char buf[MSG_SIZ];
18193     char *p, *q;
18194     int emptycount;
18195     ChessSquare piece;
18196
18197     whiteToPlay = (gameMode == EditPosition) ?
18198       !blackPlaysFirst : (move % 2 == 0);
18199     p = buf;
18200
18201     /* Piece placement data */
18202     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18203         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18204         emptycount = 0;
18205         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18206             if (boards[move][i][j] == EmptySquare) {
18207                 emptycount++;
18208             } else { ChessSquare piece = boards[move][i][j];
18209                 if (emptycount > 0) {
18210                     if(emptycount<10) /* [HGM] can be >= 10 */
18211                         *p++ = '0' + emptycount;
18212                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18213                     emptycount = 0;
18214                 }
18215                 if(PieceToChar(piece) == '+') {
18216                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18217                     *p++ = '+';
18218                     piece = (ChessSquare)(CHUDEMOTED(piece));
18219                 }
18220                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18221                 if(*p = PieceSuffix(piece)) p++;
18222                 if(p[-1] == '~') {
18223                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18224                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18225                     *p++ = '~';
18226                 }
18227             }
18228         }
18229         if (emptycount > 0) {
18230             if(emptycount<10) /* [HGM] can be >= 10 */
18231                 *p++ = '0' + emptycount;
18232             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18233             emptycount = 0;
18234         }
18235         *p++ = '/';
18236     }
18237     *(p - 1) = ' ';
18238
18239     /* [HGM] print Crazyhouse or Shogi holdings */
18240     if( gameInfo.holdingsWidth ) {
18241         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18242         q = p;
18243         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18244             piece = boards[move][i][BOARD_WIDTH-1];
18245             if( piece != EmptySquare )
18246               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18247                   *p++ = PieceToChar(piece);
18248         }
18249         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18250             piece = boards[move][BOARD_HEIGHT-i-1][0];
18251             if( piece != EmptySquare )
18252               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18253                   *p++ = PieceToChar(piece);
18254         }
18255
18256         if( q == p ) *p++ = '-';
18257         *p++ = ']';
18258         *p++ = ' ';
18259     }
18260
18261     /* Active color */
18262     *p++ = whiteToPlay ? 'w' : 'b';
18263     *p++ = ' ';
18264
18265   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18266     haveRights = 0; q = p;
18267     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18268       piece = boards[move][0][i];
18269       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18270         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18271       }
18272     }
18273     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18274       piece = boards[move][BOARD_HEIGHT-1][i];
18275       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18276         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18277       }
18278     }
18279     if(p == q) *p++ = '-';
18280     *p++ = ' ';
18281   }
18282
18283   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18284     while(*p++ = *q++)
18285                       ;
18286     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18287   } else {
18288   if(haveRights) {
18289      int handW=0, handB=0;
18290      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18291         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18292         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18293      }
18294      q = p;
18295      if(appData.fischerCastling) {
18296         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18297            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18298                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18299         } else {
18300        /* [HGM] write directly from rights */
18301            if(boards[move][CASTLING][2] != NoRights &&
18302               boards[move][CASTLING][0] != NoRights   )
18303                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18304            if(boards[move][CASTLING][2] != NoRights &&
18305               boards[move][CASTLING][1] != NoRights   )
18306                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18307         }
18308         if(handB) {
18309            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18310                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18311         } else {
18312            if(boards[move][CASTLING][5] != NoRights &&
18313               boards[move][CASTLING][3] != NoRights   )
18314                 *p++ = boards[move][CASTLING][3] + AAA;
18315            if(boards[move][CASTLING][5] != NoRights &&
18316               boards[move][CASTLING][4] != NoRights   )
18317                 *p++ = boards[move][CASTLING][4] + AAA;
18318         }
18319      } else {
18320
18321         /* [HGM] write true castling rights */
18322         if( nrCastlingRights == 6 ) {
18323             int q, k=0;
18324             if(boards[move][CASTLING][0] != NoRights &&
18325                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18326             q = (boards[move][CASTLING][1] != NoRights &&
18327                  boards[move][CASTLING][2] != NoRights  );
18328             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18329                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18330                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18331                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18332             }
18333             if(q) *p++ = 'Q';
18334             k = 0;
18335             if(boards[move][CASTLING][3] != NoRights &&
18336                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18337             q = (boards[move][CASTLING][4] != NoRights &&
18338                  boards[move][CASTLING][5] != NoRights  );
18339             if(handB) {
18340                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18341                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18342                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18343             }
18344             if(q) *p++ = 'q';
18345         }
18346      }
18347      if (q == p) *p++ = '-'; /* No castling rights */
18348      *p++ = ' ';
18349   }
18350
18351   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18352      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18353      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18354     /* En passant target square */
18355     if (move > backwardMostMove) {
18356         fromX = moveList[move - 1][0] - AAA;
18357         fromY = moveList[move - 1][1] - ONE;
18358         toX = moveList[move - 1][2] - AAA;
18359         toY = moveList[move - 1][3] - ONE;
18360         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18361             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18362             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18363             fromX == toX) {
18364             /* 2-square pawn move just happened */
18365             *p++ = toX + AAA;
18366             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18367         } else {
18368             *p++ = '-';
18369         }
18370     } else if(move == backwardMostMove) {
18371         // [HGM] perhaps we should always do it like this, and forget the above?
18372         if((signed char)boards[move][EP_STATUS] >= 0) {
18373             *p++ = boards[move][EP_STATUS] + AAA;
18374             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18375         } else {
18376             *p++ = '-';
18377         }
18378     } else {
18379         *p++ = '-';
18380     }
18381     *p++ = ' ';
18382   }
18383   }
18384
18385     if(moveCounts)
18386     {   int i = 0, j=move;
18387
18388         /* [HGM] find reversible plies */
18389         if (appData.debugMode) { int k;
18390             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18391             for(k=backwardMostMove; k<=forwardMostMove; k++)
18392                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18393
18394         }
18395
18396         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18397         if( j == backwardMostMove ) i += initialRulePlies;
18398         sprintf(p, "%d ", i);
18399         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18400
18401         /* Fullmove number */
18402         sprintf(p, "%d", (move / 2) + 1);
18403     } else *--p = NULLCHAR;
18404
18405     return StrSave(buf);
18406 }
18407
18408 Boolean
18409 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18410 {
18411     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18412     char *p, c;
18413     int emptycount, virgin[BOARD_FILES];
18414     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18415
18416     p = fen;
18417
18418     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18419
18420     /* Piece placement data */
18421     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18422         j = 0;
18423         for (;;) {
18424             if (*p == '/' || *p == ' ' || *p == '[' ) {
18425                 if(j > w) w = j;
18426                 emptycount = gameInfo.boardWidth - j;
18427                 while (emptycount--)
18428                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18429                 if (*p == '/') p++;
18430                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18431                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18432                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18433                     }
18434                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18435                 }
18436                 break;
18437 #if(BOARD_FILES >= 10)*0
18438             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18439                 p++; emptycount=10;
18440                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18441                 while (emptycount--)
18442                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18443 #endif
18444             } else if (*p == '*') {
18445                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18446             } else if (isdigit(*p)) {
18447                 emptycount = *p++ - '0';
18448                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18449                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18450                 while (emptycount--)
18451                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18452             } else if (*p == '<') {
18453                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18454                 else if (i != 0 || !shuffle) return FALSE;
18455                 p++;
18456             } else if (shuffle && *p == '>') {
18457                 p++; // for now ignore closing shuffle range, and assume rank-end
18458             } else if (*p == '?') {
18459                 if (j >= gameInfo.boardWidth) return FALSE;
18460                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18461                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18462             } else if (*p == '+' || isalpha(*p)) {
18463                 char *q, *s = SUFFIXES;
18464                 if (j >= gameInfo.boardWidth) return FALSE;
18465                 if(*p=='+') {
18466                     char c = *++p;
18467                     if(q = strchr(s, p[1])) p++;
18468                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18469                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18470                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18471                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18472                 } else {
18473                     char c = *p++;
18474                     if(q = strchr(s, *p)) p++;
18475                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18476                 }
18477
18478                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18479                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18480                     piece = (ChessSquare) (PROMOTED(piece));
18481                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18482                     p++;
18483                 }
18484                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18485                 if(piece == king) wKingRank = i;
18486                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18487             } else {
18488                 return FALSE;
18489             }
18490         }
18491     }
18492     while (*p == '/' || *p == ' ') p++;
18493
18494     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18495
18496     /* [HGM] by default clear Crazyhouse holdings, if present */
18497     if(gameInfo.holdingsWidth) {
18498        for(i=0; i<BOARD_HEIGHT; i++) {
18499            board[i][0]             = EmptySquare; /* black holdings */
18500            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18501            board[i][1]             = (ChessSquare) 0; /* black counts */
18502            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18503        }
18504     }
18505
18506     /* [HGM] look for Crazyhouse holdings here */
18507     while(*p==' ') p++;
18508     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18509         int swap=0, wcnt=0, bcnt=0;
18510         if(*p == '[') p++;
18511         if(*p == '<') swap++, p++;
18512         if(*p == '-' ) p++; /* empty holdings */ else {
18513             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18514             /* if we would allow FEN reading to set board size, we would   */
18515             /* have to add holdings and shift the board read so far here   */
18516             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18517                 p++;
18518                 if((int) piece >= (int) BlackPawn ) {
18519                     i = (int)piece - (int)BlackPawn;
18520                     i = PieceToNumber((ChessSquare)i);
18521                     if( i >= gameInfo.holdingsSize ) return FALSE;
18522                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18523                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18524                     bcnt++;
18525                 } else {
18526                     i = (int)piece - (int)WhitePawn;
18527                     i = PieceToNumber((ChessSquare)i);
18528                     if( i >= gameInfo.holdingsSize ) return FALSE;
18529                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18530                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18531                     wcnt++;
18532                 }
18533             }
18534             if(subst) { // substitute back-rank question marks by holdings pieces
18535                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18536                     int k, m, n = bcnt + 1;
18537                     if(board[0][j] == ClearBoard) {
18538                         if(!wcnt) return FALSE;
18539                         n = rand() % wcnt;
18540                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18541                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18542                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18543                             break;
18544                         }
18545                     }
18546                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18547                         if(!bcnt) return FALSE;
18548                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18549                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18550                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18551                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18552                             break;
18553                         }
18554                     }
18555                 }
18556                 subst = 0;
18557             }
18558         }
18559         if(*p == ']') p++;
18560     }
18561
18562     if(subst) return FALSE; // substitution requested, but no holdings
18563
18564     while(*p == ' ') p++;
18565
18566     /* Active color */
18567     c = *p++;
18568     if(appData.colorNickNames) {
18569       if( c == appData.colorNickNames[0] ) c = 'w'; else
18570       if( c == appData.colorNickNames[1] ) c = 'b';
18571     }
18572     switch (c) {
18573       case 'w':
18574         *blackPlaysFirst = FALSE;
18575         break;
18576       case 'b':
18577         *blackPlaysFirst = TRUE;
18578         break;
18579       default:
18580         return FALSE;
18581     }
18582
18583     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18584     /* return the extra info in global variiables             */
18585
18586     while(*p==' ') p++;
18587
18588     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18589         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18590         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18591     }
18592
18593     /* set defaults in case FEN is incomplete */
18594     board[EP_STATUS] = EP_UNKNOWN;
18595     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18596     for(i=0; i<nrCastlingRights; i++ ) {
18597         board[CASTLING][i] =
18598             appData.fischerCastling ? NoRights : initialRights[i];
18599     }   /* assume possible unless obviously impossible */
18600     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18601     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18602     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18603                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18604     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18605     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18606     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18607                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18608     FENrulePlies = 0;
18609
18610     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18611       char *q = p;
18612       int w=0, b=0;
18613       while(isalpha(*p)) {
18614         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18615         if(islower(*p)) b |= 1 << (*p++ - 'a');
18616       }
18617       if(*p == '-') p++;
18618       if(p != q) {
18619         board[TOUCHED_W] = ~w;
18620         board[TOUCHED_B] = ~b;
18621         while(*p == ' ') p++;
18622       }
18623     } else
18624
18625     if(nrCastlingRights) {
18626       int fischer = 0;
18627       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18628       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18629           /* castling indicator present, so default becomes no castlings */
18630           for(i=0; i<nrCastlingRights; i++ ) {
18631                  board[CASTLING][i] = NoRights;
18632           }
18633       }
18634       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18635              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18636              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18637              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18638         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18639
18640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18641             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18642             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18643         }
18644         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18645             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18646         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18647                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18648         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18649                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18650         switch(c) {
18651           case'K':
18652               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18653               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18654               board[CASTLING][2] = whiteKingFile;
18655               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18656               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18657               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18658               break;
18659           case'Q':
18660               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18661               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18662               board[CASTLING][2] = whiteKingFile;
18663               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18664               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18665               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18666               break;
18667           case'k':
18668               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18669               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18670               board[CASTLING][5] = blackKingFile;
18671               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18672               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18673               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18674               break;
18675           case'q':
18676               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18677               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18678               board[CASTLING][5] = blackKingFile;
18679               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18680               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18681               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18682           case '-':
18683               break;
18684           default: /* FRC castlings */
18685               if(c >= 'a') { /* black rights */
18686                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18687                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18688                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18689                   if(i == BOARD_RGHT) break;
18690                   board[CASTLING][5] = i;
18691                   c -= AAA;
18692                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18693                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18694                   if(c > i)
18695                       board[CASTLING][3] = c;
18696                   else
18697                       board[CASTLING][4] = c;
18698               } else { /* white rights */
18699                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18700                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18701                     if(board[0][i] == WhiteKing) break;
18702                   if(i == BOARD_RGHT) break;
18703                   board[CASTLING][2] = i;
18704                   c -= AAA - 'a' + 'A';
18705                   if(board[0][c] >= WhiteKing) break;
18706                   if(c > i)
18707                       board[CASTLING][0] = c;
18708                   else
18709                       board[CASTLING][1] = c;
18710               }
18711         }
18712       }
18713       for(i=0; i<nrCastlingRights; i++)
18714         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18715       if(gameInfo.variant == VariantSChess)
18716         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18717       if(fischer && shuffle) appData.fischerCastling = TRUE;
18718     if (appData.debugMode) {
18719         fprintf(debugFP, "FEN castling rights:");
18720         for(i=0; i<nrCastlingRights; i++)
18721         fprintf(debugFP, " %d", board[CASTLING][i]);
18722         fprintf(debugFP, "\n");
18723     }
18724
18725       while(*p==' ') p++;
18726     }
18727
18728     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18729
18730     /* read e.p. field in games that know e.p. capture */
18731     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18732        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18733        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18734       if(*p=='-') {
18735         p++; board[EP_STATUS] = EP_NONE;
18736       } else {
18737          char c = *p++ - AAA;
18738
18739          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18740          if(*p >= '0' && *p <='9') p++;
18741          board[EP_STATUS] = c;
18742       }
18743     }
18744
18745
18746     if(sscanf(p, "%d", &i) == 1) {
18747         FENrulePlies = i; /* 50-move ply counter */
18748         /* (The move number is still ignored)    */
18749     }
18750
18751     return TRUE;
18752 }
18753
18754 void
18755 EditPositionPasteFEN (char *fen)
18756 {
18757   if (fen != NULL) {
18758     Board initial_position;
18759
18760     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18761       DisplayError(_("Bad FEN position in clipboard"), 0);
18762       return ;
18763     } else {
18764       int savedBlackPlaysFirst = blackPlaysFirst;
18765       EditPositionEvent();
18766       blackPlaysFirst = savedBlackPlaysFirst;
18767       CopyBoard(boards[0], initial_position);
18768       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18769       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18770       DisplayBothClocks();
18771       DrawPosition(FALSE, boards[currentMove]);
18772     }
18773   }
18774 }
18775
18776 static char cseq[12] = "\\   ";
18777
18778 Boolean
18779 set_cont_sequence (char *new_seq)
18780 {
18781     int len;
18782     Boolean ret;
18783
18784     // handle bad attempts to set the sequence
18785         if (!new_seq)
18786                 return 0; // acceptable error - no debug
18787
18788     len = strlen(new_seq);
18789     ret = (len > 0) && (len < sizeof(cseq));
18790     if (ret)
18791       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18792     else if (appData.debugMode)
18793       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18794     return ret;
18795 }
18796
18797 /*
18798     reformat a source message so words don't cross the width boundary.  internal
18799     newlines are not removed.  returns the wrapped size (no null character unless
18800     included in source message).  If dest is NULL, only calculate the size required
18801     for the dest buffer.  lp argument indicats line position upon entry, and it's
18802     passed back upon exit.
18803 */
18804 int
18805 wrap (char *dest, char *src, int count, int width, int *lp)
18806 {
18807     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18808
18809     cseq_len = strlen(cseq);
18810     old_line = line = *lp;
18811     ansi = len = clen = 0;
18812
18813     for (i=0; i < count; i++)
18814     {
18815         if (src[i] == '\033')
18816             ansi = 1;
18817
18818         // if we hit the width, back up
18819         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18820         {
18821             // store i & len in case the word is too long
18822             old_i = i, old_len = len;
18823
18824             // find the end of the last word
18825             while (i && src[i] != ' ' && src[i] != '\n')
18826             {
18827                 i--;
18828                 len--;
18829             }
18830
18831             // word too long?  restore i & len before splitting it
18832             if ((old_i-i+clen) >= width)
18833             {
18834                 i = old_i;
18835                 len = old_len;
18836             }
18837
18838             // extra space?
18839             if (i && src[i-1] == ' ')
18840                 len--;
18841
18842             if (src[i] != ' ' && src[i] != '\n')
18843             {
18844                 i--;
18845                 if (len)
18846                     len--;
18847             }
18848
18849             // now append the newline and continuation sequence
18850             if (dest)
18851                 dest[len] = '\n';
18852             len++;
18853             if (dest)
18854                 strncpy(dest+len, cseq, cseq_len);
18855             len += cseq_len;
18856             line = cseq_len;
18857             clen = cseq_len;
18858             continue;
18859         }
18860
18861         if (dest)
18862             dest[len] = src[i];
18863         len++;
18864         if (!ansi)
18865             line++;
18866         if (src[i] == '\n')
18867             line = 0;
18868         if (src[i] == 'm')
18869             ansi = 0;
18870     }
18871     if (dest && appData.debugMode)
18872     {
18873         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18874             count, width, line, len, *lp);
18875         show_bytes(debugFP, src, count);
18876         fprintf(debugFP, "\ndest: ");
18877         show_bytes(debugFP, dest, len);
18878         fprintf(debugFP, "\n");
18879     }
18880     *lp = dest ? line : old_line;
18881
18882     return len;
18883 }
18884
18885 // [HGM] vari: routines for shelving variations
18886 Boolean modeRestore = FALSE;
18887
18888 void
18889 PushInner (int firstMove, int lastMove)
18890 {
18891         int i, j, nrMoves = lastMove - firstMove;
18892
18893         // push current tail of game on stack
18894         savedResult[storedGames] = gameInfo.result;
18895         savedDetails[storedGames] = gameInfo.resultDetails;
18896         gameInfo.resultDetails = NULL;
18897         savedFirst[storedGames] = firstMove;
18898         savedLast [storedGames] = lastMove;
18899         savedFramePtr[storedGames] = framePtr;
18900         framePtr -= nrMoves; // reserve space for the boards
18901         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18902             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18903             for(j=0; j<MOVE_LEN; j++)
18904                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18905             for(j=0; j<2*MOVE_LEN; j++)
18906                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18907             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18908             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18909             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18910             pvInfoList[firstMove+i-1].depth = 0;
18911             commentList[framePtr+i] = commentList[firstMove+i];
18912             commentList[firstMove+i] = NULL;
18913         }
18914
18915         storedGames++;
18916         forwardMostMove = firstMove; // truncate game so we can start variation
18917 }
18918
18919 void
18920 PushTail (int firstMove, int lastMove)
18921 {
18922         if(appData.icsActive) { // only in local mode
18923                 forwardMostMove = currentMove; // mimic old ICS behavior
18924                 return;
18925         }
18926         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18927
18928         PushInner(firstMove, lastMove);
18929         if(storedGames == 1) GreyRevert(FALSE);
18930         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18931 }
18932
18933 void
18934 PopInner (Boolean annotate)
18935 {
18936         int i, j, nrMoves;
18937         char buf[8000], moveBuf[20];
18938
18939         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18940         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18941         nrMoves = savedLast[storedGames] - currentMove;
18942         if(annotate) {
18943                 int cnt = 10;
18944                 if(!WhiteOnMove(currentMove))
18945                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18946                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18947                 for(i=currentMove; i<forwardMostMove; i++) {
18948                         if(WhiteOnMove(i))
18949                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18950                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18951                         strcat(buf, moveBuf);
18952                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18953                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18954                 }
18955                 strcat(buf, ")");
18956         }
18957         for(i=1; i<=nrMoves; i++) { // copy last variation back
18958             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18959             for(j=0; j<MOVE_LEN; j++)
18960                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18961             for(j=0; j<2*MOVE_LEN; j++)
18962                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18963             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18964             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18965             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18966             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18967             commentList[currentMove+i] = commentList[framePtr+i];
18968             commentList[framePtr+i] = NULL;
18969         }
18970         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18971         framePtr = savedFramePtr[storedGames];
18972         gameInfo.result = savedResult[storedGames];
18973         if(gameInfo.resultDetails != NULL) {
18974             free(gameInfo.resultDetails);
18975       }
18976         gameInfo.resultDetails = savedDetails[storedGames];
18977         forwardMostMove = currentMove + nrMoves;
18978 }
18979
18980 Boolean
18981 PopTail (Boolean annotate)
18982 {
18983         if(appData.icsActive) return FALSE; // only in local mode
18984         if(!storedGames) return FALSE; // sanity
18985         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18986
18987         PopInner(annotate);
18988         if(currentMove < forwardMostMove) ForwardEvent(); else
18989         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18990
18991         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18992         return TRUE;
18993 }
18994
18995 void
18996 CleanupTail ()
18997 {       // remove all shelved variations
18998         int i;
18999         for(i=0; i<storedGames; i++) {
19000             if(savedDetails[i])
19001                 free(savedDetails[i]);
19002             savedDetails[i] = NULL;
19003         }
19004         for(i=framePtr; i<MAX_MOVES; i++) {
19005                 if(commentList[i]) free(commentList[i]);
19006                 commentList[i] = NULL;
19007         }
19008         framePtr = MAX_MOVES-1;
19009         storedGames = 0;
19010 }
19011
19012 void
19013 LoadVariation (int index, char *text)
19014 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19015         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19016         int level = 0, move;
19017
19018         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19019         // first find outermost bracketing variation
19020         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19021             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19022                 if(*p == '{') wait = '}'; else
19023                 if(*p == '[') wait = ']'; else
19024                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19025                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19026             }
19027             if(*p == wait) wait = NULLCHAR; // closing ]} found
19028             p++;
19029         }
19030         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19031         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19032         end[1] = NULLCHAR; // clip off comment beyond variation
19033         ToNrEvent(currentMove-1);
19034         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19035         // kludge: use ParsePV() to append variation to game
19036         move = currentMove;
19037         ParsePV(start, TRUE, TRUE);
19038         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19039         ClearPremoveHighlights();
19040         CommentPopDown();
19041         ToNrEvent(currentMove+1);
19042 }
19043
19044 int transparency[2];
19045
19046 void
19047 LoadTheme ()
19048 {
19049     char *p, *q, buf[MSG_SIZ];
19050     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19051         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19052         ParseArgsFromString(buf);
19053         ActivateTheme(TRUE); // also redo colors
19054         return;
19055     }
19056     p = nickName;
19057     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19058     {
19059         int len;
19060         q = appData.themeNames;
19061         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19062       if(appData.useBitmaps) {
19063         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19064                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19065                 appData.liteBackTextureMode,
19066                 appData.darkBackTextureMode );
19067       } else {
19068         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false");
19069       }
19070       if(!appData.useBitmaps || transparency[0]) {
19071         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19072       }
19073       if(!appData.useBitmaps || transparency[1]) {
19074         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19075       }
19076       if(appData.useBorder) {
19077         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19078                 appData.border);
19079       } else {
19080         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19081       }
19082       if(appData.useFont) {
19083         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19084                 appData.renderPiecesWithFont,
19085                 appData.fontToPieceTable,
19086                 Col2Text(9),    // appData.fontBackColorWhite
19087                 Col2Text(10) ); // appData.fontForeColorBlack
19088       } else {
19089         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false");
19090         if(appData.pieceDirectory[0]) {
19091           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -pid \"%s\"", appData.pieceDirectory);
19092           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19093             snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19094         }
19095         if(!appData.pieceDirectory[0] && !appData.trueColors)
19096           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19097                 Col2Text(0),   // whitePieceColor
19098                 Col2Text(1) ); // blackPieceColor
19099       }
19100       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19101                 Col2Text(4),   // highlightSquareColor
19102                 Col2Text(5) ); // premoveHighlightColor
19103         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19104         if(insert != q) insert[-1] = NULLCHAR;
19105         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19106         if(q)   free(q);
19107     }
19108     ActivateTheme(FALSE);
19109 }