Erase markers before processing highlight FEN
[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     if(legal[toY][toX] == 4) return FALSE;
6706
6707     piece = boards[currentMove][fromY][fromX];
6708     if(gameInfo.variant == VariantChu) {
6709         promotionZoneSize = BOARD_HEIGHT/3;
6710         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6711     } else if(gameInfo.variant == VariantShogi) {
6712         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6713         highestPromotingPiece = (int)WhiteAlfil;
6714     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6715         promotionZoneSize = 3;
6716     }
6717
6718     // Treat Lance as Pawn when it is not representing Amazon or Lance
6719     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6720         if(piece == WhiteLance) piece = WhitePawn; else
6721         if(piece == BlackLance) piece = BlackPawn;
6722     }
6723
6724     // next weed out all moves that do not touch the promotion zone at all
6725     if((int)piece >= BlackPawn) {
6726         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6727              return FALSE;
6728         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6729         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6730     } else {
6731         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6732            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6733         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6734              return FALSE;
6735     }
6736
6737     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6738
6739     // weed out mandatory Shogi promotions
6740     if(gameInfo.variant == VariantShogi) {
6741         if(piece >= BlackPawn) {
6742             if(toY == 0 && piece == BlackPawn ||
6743                toY == 0 && piece == BlackQueen ||
6744                toY <= 1 && piece == BlackKnight) {
6745                 *promoChoice = '+';
6746                 return FALSE;
6747             }
6748         } else {
6749             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6750                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6751                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6752                 *promoChoice = '+';
6753                 return FALSE;
6754             }
6755         }
6756     }
6757
6758     // weed out obviously illegal Pawn moves
6759     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6760         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6761         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6762         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6763         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6764         // note we are not allowed to test for valid (non-)capture, due to premove
6765     }
6766
6767     // we either have a choice what to promote to, or (in Shogi) whether to promote
6768     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6769        gameInfo.variant == VariantMakruk) {
6770         ChessSquare p=BlackFerz;  // no choice
6771         while(p < EmptySquare) {  //but make sure we use piece that exists
6772             *promoChoice = PieceToChar(p++);
6773             if(*promoChoice != '.') break;
6774         }
6775         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6776     }
6777     // no sense asking what we must promote to if it is going to explode...
6778     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6779         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6780         return FALSE;
6781     }
6782     // give caller the default choice even if we will not make it
6783     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6784     partner = piece; // pieces can promote if the pieceToCharTable says so
6785     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6786     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6787     if(        sweepSelect && gameInfo.variant != VariantGreat
6788                            && gameInfo.variant != VariantGrand
6789                            && gameInfo.variant != VariantSuper) return FALSE;
6790     if(autoQueen) return FALSE; // predetermined
6791
6792     // suppress promotion popup on illegal moves that are not premoves
6793     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6794               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6795     if(appData.testLegality && !premove) {
6796         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6797                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6798         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6799         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6800             return FALSE;
6801     }
6802
6803     return TRUE;
6804 }
6805
6806 int
6807 InPalace (int row, int column)
6808 {   /* [HGM] for Xiangqi */
6809     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6810          column < (BOARD_WIDTH + 4)/2 &&
6811          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6812     return FALSE;
6813 }
6814
6815 int
6816 PieceForSquare (int x, int y)
6817 {
6818   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6819      return -1;
6820   else
6821      return boards[currentMove][y][x];
6822 }
6823
6824 int
6825 OKToStartUserMove (int x, int y)
6826 {
6827     ChessSquare from_piece;
6828     int white_piece;
6829
6830     if (matchMode) return FALSE;
6831     if (gameMode == EditPosition) return TRUE;
6832
6833     if (x >= 0 && y >= 0)
6834       from_piece = boards[currentMove][y][x];
6835     else
6836       from_piece = EmptySquare;
6837
6838     if (from_piece == EmptySquare) return FALSE;
6839
6840     white_piece = (int)from_piece >= (int)WhitePawn &&
6841       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6842
6843     switch (gameMode) {
6844       case AnalyzeFile:
6845       case TwoMachinesPlay:
6846       case EndOfGame:
6847         return FALSE;
6848
6849       case IcsObserving:
6850       case IcsIdle:
6851         return FALSE;
6852
6853       case MachinePlaysWhite:
6854       case IcsPlayingBlack:
6855         if (appData.zippyPlay) return FALSE;
6856         if (white_piece) {
6857             DisplayMoveError(_("You are playing Black"));
6858             return FALSE;
6859         }
6860         break;
6861
6862       case MachinePlaysBlack:
6863       case IcsPlayingWhite:
6864         if (appData.zippyPlay) return FALSE;
6865         if (!white_piece) {
6866             DisplayMoveError(_("You are playing White"));
6867             return FALSE;
6868         }
6869         break;
6870
6871       case PlayFromGameFile:
6872             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6873       case EditGame:
6874       case AnalyzeMode:
6875         if (!white_piece && WhiteOnMove(currentMove)) {
6876             DisplayMoveError(_("It is White's turn"));
6877             return FALSE;
6878         }
6879         if (white_piece && !WhiteOnMove(currentMove)) {
6880             DisplayMoveError(_("It is Black's turn"));
6881             return FALSE;
6882         }
6883         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6884             /* Editing correspondence game history */
6885             /* Could disallow this or prompt for confirmation */
6886             cmailOldMove = -1;
6887         }
6888         break;
6889
6890       case BeginningOfGame:
6891         if (appData.icsActive) return FALSE;
6892         if (!appData.noChessProgram) {
6893             if (!white_piece) {
6894                 DisplayMoveError(_("You are playing White"));
6895                 return FALSE;
6896             }
6897         }
6898         break;
6899
6900       case Training:
6901         if (!white_piece && WhiteOnMove(currentMove)) {
6902             DisplayMoveError(_("It is White's turn"));
6903             return FALSE;
6904         }
6905         if (white_piece && !WhiteOnMove(currentMove)) {
6906             DisplayMoveError(_("It is Black's turn"));
6907             return FALSE;
6908         }
6909         break;
6910
6911       default:
6912       case IcsExamining:
6913         break;
6914     }
6915     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6916         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6917         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6918         && gameMode != AnalyzeFile && gameMode != Training) {
6919         DisplayMoveError(_("Displayed position is not current"));
6920         return FALSE;
6921     }
6922     return TRUE;
6923 }
6924
6925 Boolean
6926 OnlyMove (int *x, int *y, Boolean captures)
6927 {
6928     DisambiguateClosure cl;
6929     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6930     switch(gameMode) {
6931       case MachinePlaysBlack:
6932       case IcsPlayingWhite:
6933       case BeginningOfGame:
6934         if(!WhiteOnMove(currentMove)) return FALSE;
6935         break;
6936       case MachinePlaysWhite:
6937       case IcsPlayingBlack:
6938         if(WhiteOnMove(currentMove)) return FALSE;
6939         break;
6940       case EditGame:
6941         break;
6942       default:
6943         return FALSE;
6944     }
6945     cl.pieceIn = EmptySquare;
6946     cl.rfIn = *y;
6947     cl.ffIn = *x;
6948     cl.rtIn = -1;
6949     cl.ftIn = -1;
6950     cl.promoCharIn = NULLCHAR;
6951     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6952     if( cl.kind == NormalMove ||
6953         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6954         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6955         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6956       fromX = cl.ff;
6957       fromY = cl.rf;
6958       *x = cl.ft;
6959       *y = cl.rt;
6960       return TRUE;
6961     }
6962     if(cl.kind != ImpossibleMove) return FALSE;
6963     cl.pieceIn = EmptySquare;
6964     cl.rfIn = -1;
6965     cl.ffIn = -1;
6966     cl.rtIn = *y;
6967     cl.ftIn = *x;
6968     cl.promoCharIn = NULLCHAR;
6969     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6970     if( cl.kind == NormalMove ||
6971         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6972         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6973         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6974       fromX = cl.ff;
6975       fromY = cl.rf;
6976       *x = cl.ft;
6977       *y = cl.rt;
6978       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6979       return TRUE;
6980     }
6981     return FALSE;
6982 }
6983
6984 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6985 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6986 int lastLoadGameUseList = FALSE;
6987 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6988 ChessMove lastLoadGameStart = EndOfFile;
6989 int doubleClick;
6990 Boolean addToBookFlag;
6991 static Board rightsBoard, nullBoard;
6992
6993 void
6994 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6995 {
6996     ChessMove moveType;
6997     ChessSquare pup;
6998     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6999
7000     /* Check if the user is playing in turn.  This is complicated because we
7001        let the user "pick up" a piece before it is his turn.  So the piece he
7002        tried to pick up may have been captured by the time he puts it down!
7003        Therefore we use the color the user is supposed to be playing in this
7004        test, not the color of the piece that is currently on the starting
7005        square---except in EditGame mode, where the user is playing both
7006        sides; fortunately there the capture race can't happen.  (It can
7007        now happen in IcsExamining mode, but that's just too bad.  The user
7008        will get a somewhat confusing message in that case.)
7009        */
7010
7011     switch (gameMode) {
7012       case AnalyzeFile:
7013       case TwoMachinesPlay:
7014       case EndOfGame:
7015       case IcsObserving:
7016       case IcsIdle:
7017         /* We switched into a game mode where moves are not accepted,
7018            perhaps while the mouse button was down. */
7019         return;
7020
7021       case MachinePlaysWhite:
7022         /* User is moving for Black */
7023         if (WhiteOnMove(currentMove)) {
7024             DisplayMoveError(_("It is White's turn"));
7025             return;
7026         }
7027         break;
7028
7029       case MachinePlaysBlack:
7030         /* User is moving for White */
7031         if (!WhiteOnMove(currentMove)) {
7032             DisplayMoveError(_("It is Black's turn"));
7033             return;
7034         }
7035         break;
7036
7037       case PlayFromGameFile:
7038             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7039       case EditGame:
7040       case IcsExamining:
7041       case BeginningOfGame:
7042       case AnalyzeMode:
7043       case Training:
7044         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7045         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7046             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7047             /* User is moving for Black */
7048             if (WhiteOnMove(currentMove)) {
7049                 DisplayMoveError(_("It is White's turn"));
7050                 return;
7051             }
7052         } else {
7053             /* User is moving for White */
7054             if (!WhiteOnMove(currentMove)) {
7055                 DisplayMoveError(_("It is Black's turn"));
7056                 return;
7057             }
7058         }
7059         break;
7060
7061       case IcsPlayingBlack:
7062         /* User is moving for Black */
7063         if (WhiteOnMove(currentMove)) {
7064             if (!appData.premove) {
7065                 DisplayMoveError(_("It is White's turn"));
7066             } else if (toX >= 0 && toY >= 0) {
7067                 premoveToX = toX;
7068                 premoveToY = toY;
7069                 premoveFromX = fromX;
7070                 premoveFromY = fromY;
7071                 premovePromoChar = promoChar;
7072                 gotPremove = 1;
7073                 if (appData.debugMode)
7074                     fprintf(debugFP, "Got premove: fromX %d,"
7075                             "fromY %d, toX %d, toY %d\n",
7076                             fromX, fromY, toX, toY);
7077             }
7078             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7079             return;
7080         }
7081         break;
7082
7083       case IcsPlayingWhite:
7084         /* User is moving for White */
7085         if (!WhiteOnMove(currentMove)) {
7086             if (!appData.premove) {
7087                 DisplayMoveError(_("It is Black's turn"));
7088             } else if (toX >= 0 && toY >= 0) {
7089                 premoveToX = toX;
7090                 premoveToY = toY;
7091                 premoveFromX = fromX;
7092                 premoveFromY = fromY;
7093                 premovePromoChar = promoChar;
7094                 gotPremove = 1;
7095                 if (appData.debugMode)
7096                     fprintf(debugFP, "Got premove: fromX %d,"
7097                             "fromY %d, toX %d, toY %d\n",
7098                             fromX, fromY, toX, toY);
7099             }
7100             DrawPosition(TRUE, boards[currentMove]);
7101             return;
7102         }
7103         break;
7104
7105       default:
7106         break;
7107
7108       case EditPosition:
7109         /* EditPosition, empty square, or different color piece;
7110            click-click move is possible */
7111         if (toX == -2 || toY == -2) {
7112             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7113             DrawPosition(FALSE, boards[currentMove]);
7114             return;
7115         } else if (toX >= 0 && toY >= 0) {
7116             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7117                 ChessSquare p = boards[0][rf][ff];
7118                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7119                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7120                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7121                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7122                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7123                     gatingPiece = p;
7124                 }
7125             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7126             boards[0][toY][toX] = boards[0][fromY][fromX];
7127             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7128                 if(boards[0][fromY][0] != EmptySquare) {
7129                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7130                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7131                 }
7132             } else
7133             if(fromX == BOARD_RGHT+1) {
7134                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7135                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7136                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7137                 }
7138             } else
7139             boards[0][fromY][fromX] = gatingPiece;
7140             ClearHighlights();
7141             DrawPosition(FALSE, boards[currentMove]);
7142             return;
7143         }
7144         return;
7145     }
7146
7147     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7148     pup = boards[currentMove][toY][toX];
7149
7150     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7151     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7152          if( pup != EmptySquare ) return;
7153          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7154            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7155                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7156            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7157            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7158            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7159            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7160          fromY = DROP_RANK;
7161     }
7162
7163     /* [HGM] always test for legality, to get promotion info */
7164     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7165                                          fromY, fromX, toY, toX, promoChar);
7166
7167     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7168
7169     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7170
7171     /* [HGM] but possibly ignore an IllegalMove result */
7172     if (appData.testLegality) {
7173         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7174             DisplayMoveError(_("Illegal move"));
7175             return;
7176         }
7177     }
7178
7179     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7180         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7181              ClearPremoveHighlights(); // was included
7182         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7183         DrawPosition(FALSE, NULL);
7184         return;
7185     }
7186
7187     if(addToBookFlag) { // adding moves to book
7188         char buf[MSG_SIZ], move[MSG_SIZ];
7189         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7190         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7191                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7192         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7193         AddBookMove(buf);
7194         addToBookFlag = FALSE;
7195         ClearHighlights();
7196         return;
7197     }
7198
7199     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7200 }
7201
7202 /* Common tail of UserMoveEvent and DropMenuEvent */
7203 int
7204 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7205 {
7206     char *bookHit = 0;
7207
7208     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7209         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7210         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7211         if(WhiteOnMove(currentMove)) {
7212             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7213         } else {
7214             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7215         }
7216     }
7217
7218     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7219        move type in caller when we know the move is a legal promotion */
7220     if(moveType == NormalMove && promoChar)
7221         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7222
7223     /* [HGM] <popupFix> The following if has been moved here from
7224        UserMoveEvent(). Because it seemed to belong here (why not allow
7225        piece drops in training games?), and because it can only be
7226        performed after it is known to what we promote. */
7227     if (gameMode == Training) {
7228       /* compare the move played on the board to the next move in the
7229        * game. If they match, display the move and the opponent's response.
7230        * If they don't match, display an error message.
7231        */
7232       int saveAnimate;
7233       Board testBoard;
7234       CopyBoard(testBoard, boards[currentMove]);
7235       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7236
7237       if (CompareBoards(testBoard, boards[currentMove+1])) {
7238         ForwardInner(currentMove+1);
7239
7240         /* Autoplay the opponent's response.
7241          * if appData.animate was TRUE when Training mode was entered,
7242          * the response will be animated.
7243          */
7244         saveAnimate = appData.animate;
7245         appData.animate = animateTraining;
7246         ForwardInner(currentMove+1);
7247         appData.animate = saveAnimate;
7248
7249         /* check for the end of the game */
7250         if (currentMove >= forwardMostMove) {
7251           gameMode = PlayFromGameFile;
7252           ModeHighlight();
7253           SetTrainingModeOff();
7254           DisplayInformation(_("End of game"));
7255         }
7256       } else {
7257         DisplayError(_("Incorrect move"), 0);
7258       }
7259       return 1;
7260     }
7261
7262   /* Ok, now we know that the move is good, so we can kill
7263      the previous line in Analysis Mode */
7264   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7265                                 && currentMove < forwardMostMove) {
7266     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7267     else forwardMostMove = currentMove;
7268   }
7269
7270   ClearMap();
7271
7272   /* If we need the chess program but it's dead, restart it */
7273   ResurrectChessProgram();
7274
7275   /* A user move restarts a paused game*/
7276   if (pausing)
7277     PauseEvent();
7278
7279   thinkOutput[0] = NULLCHAR;
7280
7281   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7282
7283   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7284     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7285     return 1;
7286   }
7287
7288   if (gameMode == BeginningOfGame) {
7289     if (appData.noChessProgram) {
7290       gameMode = EditGame;
7291       SetGameInfo();
7292     } else {
7293       char buf[MSG_SIZ];
7294       gameMode = MachinePlaysBlack;
7295       StartClocks();
7296       SetGameInfo();
7297       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7298       DisplayTitle(buf);
7299       if (first.sendName) {
7300         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7301         SendToProgram(buf, &first);
7302       }
7303       StartClocks();
7304     }
7305     ModeHighlight();
7306   }
7307
7308   /* Relay move to ICS or chess engine */
7309   if (appData.icsActive) {
7310     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7311         gameMode == IcsExamining) {
7312       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7313         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7314         SendToICS("draw ");
7315         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7316       }
7317       // also send plain move, in case ICS does not understand atomic claims
7318       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7319       ics_user_moved = 1;
7320     }
7321   } else {
7322     if (first.sendTime && (gameMode == BeginningOfGame ||
7323                            gameMode == MachinePlaysWhite ||
7324                            gameMode == MachinePlaysBlack)) {
7325       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7326     }
7327     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7328          // [HGM] book: if program might be playing, let it use book
7329         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7330         first.maybeThinking = TRUE;
7331     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7332         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7333         SendBoard(&first, currentMove+1);
7334         if(second.analyzing) {
7335             if(!second.useSetboard) SendToProgram("undo\n", &second);
7336             SendBoard(&second, currentMove+1);
7337         }
7338     } else {
7339         SendMoveToProgram(forwardMostMove-1, &first);
7340         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7341     }
7342     if (currentMove == cmailOldMove + 1) {
7343       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7344     }
7345   }
7346
7347   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7348
7349   switch (gameMode) {
7350   case EditGame:
7351     if(appData.testLegality)
7352     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7353     case MT_NONE:
7354     case MT_CHECK:
7355       break;
7356     case MT_CHECKMATE:
7357     case MT_STAINMATE:
7358       if (WhiteOnMove(currentMove)) {
7359         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7360       } else {
7361         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7362       }
7363       break;
7364     case MT_STALEMATE:
7365       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7366       break;
7367     }
7368     break;
7369
7370   case MachinePlaysBlack:
7371   case MachinePlaysWhite:
7372     /* disable certain menu options while machine is thinking */
7373     SetMachineThinkingEnables();
7374     break;
7375
7376   default:
7377     break;
7378   }
7379
7380   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7381   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7382
7383   if(bookHit) { // [HGM] book: simulate book reply
7384         static char bookMove[MSG_SIZ]; // a bit generous?
7385
7386         programStats.nodes = programStats.depth = programStats.time =
7387         programStats.score = programStats.got_only_move = 0;
7388         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7389
7390         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7391         strcat(bookMove, bookHit);
7392         HandleMachineMove(bookMove, &first);
7393   }
7394   return 1;
7395 }
7396
7397 void
7398 MarkByFEN(char *fen)
7399 {
7400         int r, f;
7401         if(!appData.markers || !appData.highlightDragging) return;
7402         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7403         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7404         while(*fen) {
7405             int s = 0;
7406             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7407             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7408             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7409             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7410             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7411             if(*fen == 'T') marker[r][f++] = 0; else
7412             if(*fen == 'Y') marker[r][f++] = 1; else
7413             if(*fen == 'G') marker[r][f++] = 3; else
7414             if(*fen == 'B') marker[r][f++] = 4; else
7415             if(*fen == 'C') marker[r][f++] = 5; else
7416             if(*fen == 'M') marker[r][f++] = 6; else
7417             if(*fen == 'W') marker[r][f++] = 7; else
7418             if(*fen == 'D') marker[r][f++] = 8; else
7419             if(*fen == 'R') marker[r][f++] = 2; else {
7420                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7421               f += s; fen -= s>0;
7422             }
7423             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7424             if(r < 0) break;
7425             fen++;
7426         }
7427         DrawPosition(TRUE, NULL);
7428 }
7429
7430 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7431
7432 void
7433 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7434 {
7435     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7436     Markers *m = (Markers *) closure;
7437     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7438                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7439         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7440                          || kind == WhiteCapturesEnPassant
7441                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7442     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7443 }
7444
7445 static int hoverSavedValid;
7446
7447 void
7448 MarkTargetSquares (int clear)
7449 {
7450   int x, y, sum=0;
7451   if(clear) { // no reason to ever suppress clearing
7452     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7453     hoverSavedValid = 0;
7454     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7455   } else {
7456     int capt = 0;
7457     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7458        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7459     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7460     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7461       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7462       if(capt)
7463       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7464     }
7465   }
7466   DrawPosition(FALSE, NULL);
7467 }
7468
7469 int
7470 Explode (Board board, int fromX, int fromY, int toX, int toY)
7471 {
7472     if(gameInfo.variant == VariantAtomic &&
7473        (board[toY][toX] != EmptySquare ||                     // capture?
7474         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7475                          board[fromY][fromX] == BlackPawn   )
7476       )) {
7477         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7478         return TRUE;
7479     }
7480     return FALSE;
7481 }
7482
7483 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7484
7485 int
7486 CanPromote (ChessSquare piece, int y)
7487 {
7488         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7489         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7490         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7491         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7492            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7493           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7494            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7495         return (piece == BlackPawn && y <= zone ||
7496                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7497                 piece == BlackLance && y <= zone ||
7498                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7499 }
7500
7501 void
7502 HoverEvent (int xPix, int yPix, int x, int y)
7503 {
7504         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7505         int r, f;
7506         if(!first.highlight) return;
7507         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7508         if(x == oldX && y == oldY) return; // only do something if we enter new square
7509         oldFromX = fromX; oldFromY = fromY;
7510         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7511           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7512             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7513           hoverSavedValid = 1;
7514         } else if(oldX != x || oldY != y) {
7515           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7516           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7517           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7518             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7519           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7520             char buf[MSG_SIZ];
7521             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7522             SendToProgram(buf, &first);
7523           }
7524           oldX = x; oldY = y;
7525 //        SetHighlights(fromX, fromY, x, y);
7526         }
7527 }
7528
7529 void ReportClick(char *action, int x, int y)
7530 {
7531         char buf[MSG_SIZ]; // Inform engine of what user does
7532         int r, f;
7533         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7534           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7535             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7536         if(!first.highlight || gameMode == EditPosition) return;
7537         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7538         SendToProgram(buf, &first);
7539 }
7540
7541 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7542 Boolean deferChoice;
7543
7544 void
7545 LeftClick (ClickType clickType, int xPix, int yPix)
7546 {
7547     int x, y;
7548     static Boolean saveAnimate;
7549     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7550     char promoChoice = NULLCHAR;
7551     ChessSquare piece;
7552     static TimeMark lastClickTime, prevClickTime;
7553
7554     if(flashing) return;
7555
7556   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7557     x = EventToSquare(xPix, BOARD_WIDTH);
7558     y = EventToSquare(yPix, BOARD_HEIGHT);
7559     if (!flipView && y >= 0) {
7560         y = BOARD_HEIGHT - 1 - y;
7561     }
7562     if (flipView && x >= 0) {
7563         x = BOARD_WIDTH - 1 - x;
7564     }
7565
7566     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7567         static int dummy;
7568         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7569         right = TRUE;
7570         return;
7571     }
7572
7573     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7574
7575     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7576
7577     if (clickType == Press) ErrorPopDown();
7578     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7579
7580     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7581         defaultPromoChoice = promoSweep;
7582         promoSweep = EmptySquare;   // terminate sweep
7583         promoDefaultAltered = TRUE;
7584         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7585     }
7586
7587     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7588         if(clickType == Release) return; // ignore upclick of click-click destination
7589         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7590         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7591         if(gameInfo.holdingsWidth &&
7592                 (WhiteOnMove(currentMove)
7593                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7594                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7595             // click in right holdings, for determining promotion piece
7596             ChessSquare p = boards[currentMove][y][x];
7597             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7598             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7599             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7600                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7601                 fromX = fromY = -1;
7602                 return;
7603             }
7604         }
7605         DrawPosition(FALSE, boards[currentMove]);
7606         return;
7607     }
7608
7609     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7610     if(clickType == Press
7611             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7612               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7613               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7614         return;
7615
7616     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7617         // could be static click on premove from-square: abort premove
7618         gotPremove = 0;
7619         ClearPremoveHighlights();
7620     }
7621
7622     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7623         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7624
7625     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7626         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7627                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7628         defaultPromoChoice = DefaultPromoChoice(side);
7629     }
7630
7631     autoQueen = appData.alwaysPromoteToQueen;
7632
7633     if (fromX == -1) {
7634       int originalY = y;
7635       gatingPiece = EmptySquare;
7636       if (clickType != Press) {
7637         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7638             DragPieceEnd(xPix, yPix); dragging = 0;
7639             DrawPosition(FALSE, NULL);
7640         }
7641         return;
7642       }
7643       doubleClick = FALSE;
7644       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7645         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7646       }
7647       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7648       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7649          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7650          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7651             /* First square */
7652             if (OKToStartUserMove(fromX, fromY)) {
7653                 second = 0;
7654                 ReportClick("lift", x, y);
7655                 MarkTargetSquares(0);
7656                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7657                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7658                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7659                     promoSweep = defaultPromoChoice;
7660                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7661                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7662                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7663                 }
7664                 if (appData.highlightDragging) {
7665                     SetHighlights(fromX, fromY, -1, -1);
7666                 } else {
7667                     ClearHighlights();
7668                 }
7669             } else fromX = fromY = -1;
7670             return;
7671         }
7672     }
7673
7674     /* fromX != -1 */
7675     if (clickType == Press && gameMode != EditPosition) {
7676         ChessSquare fromP;
7677         ChessSquare toP;
7678         int frc;
7679
7680         // ignore off-board to clicks
7681         if(y < 0 || x < 0) return;
7682
7683         /* Check if clicking again on the same color piece */
7684         fromP = boards[currentMove][fromY][fromX];
7685         toP = boards[currentMove][y][x];
7686         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7687         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7688             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7689            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7690              WhitePawn <= toP && toP <= WhiteKing &&
7691              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7692              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7693             (BlackPawn <= fromP && fromP <= BlackKing &&
7694              BlackPawn <= toP && toP <= BlackKing &&
7695              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7696              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7697             /* Clicked again on same color piece -- changed his mind */
7698             second = (x == fromX && y == fromY);
7699             killX = killY = kill2X = kill2Y = -1;
7700             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7701                 second = FALSE; // first double-click rather than scond click
7702                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7703             }
7704             promoDefaultAltered = FALSE;
7705            if(!second) MarkTargetSquares(1);
7706            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7707             if (appData.highlightDragging) {
7708                 SetHighlights(x, y, -1, -1);
7709             } else {
7710                 ClearHighlights();
7711             }
7712             if (OKToStartUserMove(x, y)) {
7713                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7714                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7715                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7716                  gatingPiece = boards[currentMove][fromY][fromX];
7717                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7718                 fromX = x;
7719                 fromY = y; dragging = 1;
7720                 if(!second) ReportClick("lift", x, y);
7721                 MarkTargetSquares(0);
7722                 DragPieceBegin(xPix, yPix, FALSE);
7723                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7724                     promoSweep = defaultPromoChoice;
7725                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7726                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7727                 }
7728             }
7729            }
7730            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7731            second = FALSE;
7732         }
7733         // ignore clicks on holdings
7734         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7735     }
7736
7737     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7738         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7739         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7740         return;
7741     }
7742
7743     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7744         DragPieceEnd(xPix, yPix); dragging = 0;
7745         if(clearFlag) {
7746             // a deferred attempt to click-click move an empty square on top of a piece
7747             boards[currentMove][y][x] = EmptySquare;
7748             ClearHighlights();
7749             DrawPosition(FALSE, boards[currentMove]);
7750             fromX = fromY = -1; clearFlag = 0;
7751             return;
7752         }
7753         if (appData.animateDragging) {
7754             /* Undo animation damage if any */
7755             DrawPosition(FALSE, NULL);
7756         }
7757         if (second) {
7758             /* Second up/down in same square; just abort move */
7759             second = 0;
7760             fromX = fromY = -1;
7761             gatingPiece = EmptySquare;
7762             ClearHighlights();
7763             gotPremove = 0;
7764             ClearPremoveHighlights();
7765             MarkTargetSquares(-1);
7766             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7767         } else {
7768             /* First upclick in same square; start click-click mode */
7769             SetHighlights(x, y, -1, -1);
7770         }
7771         return;
7772     }
7773
7774     clearFlag = 0;
7775
7776     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7777        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7778         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7779         DisplayMessage(_("only marked squares are legal"),"");
7780         DrawPosition(TRUE, NULL);
7781         return; // ignore to-click
7782     }
7783
7784     /* we now have a different from- and (possibly off-board) to-square */
7785     /* Completed move */
7786     if(!sweepSelecting) {
7787         toX = x;
7788         toY = y;
7789     }
7790
7791     piece = boards[currentMove][fromY][fromX];
7792
7793     saveAnimate = appData.animate;
7794     if (clickType == Press) {
7795         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7796         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7797             // must be Edit Position mode with empty-square selected
7798             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7799             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7800             return;
7801         }
7802         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7803             return;
7804         }
7805         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7806             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7807         } else
7808         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7809         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7810           if(appData.sweepSelect) {
7811             promoSweep = defaultPromoChoice;
7812             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7813             selectFlag = 0; lastX = xPix; lastY = yPix;
7814             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7815             saveFlash = appData.flashCount; appData.flashCount = 0;
7816             Sweep(0); // Pawn that is going to promote: preview promotion piece
7817             sweepSelecting = 1;
7818             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7819             MarkTargetSquares(1);
7820           }
7821           return; // promo popup appears on up-click
7822         }
7823         /* Finish clickclick move */
7824         if (appData.animate || appData.highlightLastMove) {
7825             SetHighlights(fromX, fromY, toX, toY);
7826         } else {
7827             ClearHighlights();
7828         }
7829         MarkTargetSquares(1);
7830     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7831         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7832         *promoRestrict = 0; appData.flashCount = saveFlash;
7833         if (appData.animate || appData.highlightLastMove) {
7834             SetHighlights(fromX, fromY, toX, toY);
7835         } else {
7836             ClearHighlights();
7837         }
7838         MarkTargetSquares(1);
7839     } else {
7840 #if 0
7841 // [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
7842         /* Finish drag move */
7843         if (appData.highlightLastMove) {
7844             SetHighlights(fromX, fromY, toX, toY);
7845         } else {
7846             ClearHighlights();
7847         }
7848 #endif
7849         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7850           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7851         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7852         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7853           dragging *= 2;            // flag button-less dragging if we are dragging
7854           MarkTargetSquares(1);
7855           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7856           else {
7857             kill2X = killX; kill2Y = killY;
7858             killX = x; killY = y;     // remember this square as intermediate
7859             ReportClick("put", x, y); // and inform engine
7860             ReportClick("lift", x, y);
7861             MarkTargetSquares(0);
7862             return;
7863           }
7864         }
7865         DragPieceEnd(xPix, yPix); dragging = 0;
7866         /* Don't animate move and drag both */
7867         appData.animate = FALSE;
7868         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7869     }
7870
7871     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7872     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7873         ChessSquare piece = boards[currentMove][fromY][fromX];
7874         if(gameMode == EditPosition && piece != EmptySquare &&
7875            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7876             int n;
7877
7878             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7879                 n = PieceToNumber(piece - (int)BlackPawn);
7880                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7881                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7882                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7883             } else
7884             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7885                 n = PieceToNumber(piece);
7886                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7887                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7888                 boards[currentMove][n][BOARD_WIDTH-2]++;
7889             }
7890             boards[currentMove][fromY][fromX] = EmptySquare;
7891         }
7892         ClearHighlights();
7893         fromX = fromY = -1;
7894         MarkTargetSquares(1);
7895         DrawPosition(TRUE, boards[currentMove]);
7896         return;
7897     }
7898
7899     // off-board moves should not be highlighted
7900     if(x < 0 || y < 0) ClearHighlights();
7901     else ReportClick("put", x, y);
7902
7903     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7904  }
7905
7906     if(legal[toY][toX] == 2) { // highlight-induced promotion
7907         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7908         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7909     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7910       if(!*promoRestrict) {           // but has not done that yet
7911         deferChoice = TRUE;           // set up retry for when it does
7912         return;                       // and wait for that
7913       }
7914       promoChoice = ToLower(*promoRestrict); // force engine's choice
7915       deferChoice = FALSE;
7916     }
7917
7918     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7919         SetHighlights(fromX, fromY, toX, toY);
7920         MarkTargetSquares(1);
7921         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7922             // [HGM] super: promotion to captured piece selected from holdings
7923             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7924             promotionChoice = TRUE;
7925             // kludge follows to temporarily execute move on display, without promoting yet
7926             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7927             boards[currentMove][toY][toX] = p;
7928             DrawPosition(FALSE, boards[currentMove]);
7929             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7930             boards[currentMove][toY][toX] = q;
7931             DisplayMessage("Click in holdings to choose piece", "");
7932             return;
7933         }
7934         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7935         PromotionPopUp(promoChoice);
7936     } else {
7937         int oldMove = currentMove;
7938         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7939         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7940         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7941         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7942         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7943            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7944             DrawPosition(TRUE, boards[currentMove]);
7945         fromX = fromY = -1;
7946         flashing = 0;
7947     }
7948     appData.animate = saveAnimate;
7949     if (appData.animate || appData.animateDragging) {
7950         /* Undo animation damage if needed */
7951 //      DrawPosition(FALSE, NULL);
7952     }
7953 }
7954
7955 int
7956 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7957 {   // front-end-free part taken out of PieceMenuPopup
7958     int whichMenu; int xSqr, ySqr;
7959
7960     if(seekGraphUp) { // [HGM] seekgraph
7961         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7962         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7963         return -2;
7964     }
7965
7966     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7967          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7968         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7969         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7970         if(action == Press)   {
7971             originalFlip = flipView;
7972             flipView = !flipView; // temporarily flip board to see game from partners perspective
7973             DrawPosition(TRUE, partnerBoard);
7974             DisplayMessage(partnerStatus, "");
7975             partnerUp = TRUE;
7976         } else if(action == Release) {
7977             flipView = originalFlip;
7978             DrawPosition(TRUE, boards[currentMove]);
7979             partnerUp = FALSE;
7980         }
7981         return -2;
7982     }
7983
7984     xSqr = EventToSquare(x, BOARD_WIDTH);
7985     ySqr = EventToSquare(y, BOARD_HEIGHT);
7986     if (action == Release) {
7987         if(pieceSweep != EmptySquare) {
7988             EditPositionMenuEvent(pieceSweep, toX, toY);
7989             pieceSweep = EmptySquare;
7990         } else UnLoadPV(); // [HGM] pv
7991     }
7992     if (action != Press) return -2; // return code to be ignored
7993     switch (gameMode) {
7994       case IcsExamining:
7995         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7996       case EditPosition:
7997         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7998         if (xSqr < 0 || ySqr < 0) return -1;
7999         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8000         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8001         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8002         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8003         NextPiece(0);
8004         return 2; // grab
8005       case IcsObserving:
8006         if(!appData.icsEngineAnalyze) return -1;
8007       case IcsPlayingWhite:
8008       case IcsPlayingBlack:
8009         if(!appData.zippyPlay) goto noZip;
8010       case AnalyzeMode:
8011       case AnalyzeFile:
8012       case MachinePlaysWhite:
8013       case MachinePlaysBlack:
8014       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8015         if (!appData.dropMenu) {
8016           LoadPV(x, y);
8017           return 2; // flag front-end to grab mouse events
8018         }
8019         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8020            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8021       case EditGame:
8022       noZip:
8023         if (xSqr < 0 || ySqr < 0) return -1;
8024         if (!appData.dropMenu || appData.testLegality &&
8025             gameInfo.variant != VariantBughouse &&
8026             gameInfo.variant != VariantCrazyhouse) return -1;
8027         whichMenu = 1; // drop menu
8028         break;
8029       default:
8030         return -1;
8031     }
8032
8033     if (((*fromX = xSqr) < 0) ||
8034         ((*fromY = ySqr) < 0)) {
8035         *fromX = *fromY = -1;
8036         return -1;
8037     }
8038     if (flipView)
8039       *fromX = BOARD_WIDTH - 1 - *fromX;
8040     else
8041       *fromY = BOARD_HEIGHT - 1 - *fromY;
8042
8043     return whichMenu;
8044 }
8045
8046 void
8047 Wheel (int dir, int x, int y)
8048 {
8049     if(gameMode == EditPosition) {
8050         int xSqr = EventToSquare(x, BOARD_WIDTH);
8051         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8052         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8053         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8054         do {
8055             boards[currentMove][ySqr][xSqr] += dir;
8056             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8057             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8058         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8059         DrawPosition(FALSE, boards[currentMove]);
8060     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8061 }
8062
8063 void
8064 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8065 {
8066 //    char * hint = lastHint;
8067     FrontEndProgramStats stats;
8068
8069     stats.which = cps == &first ? 0 : 1;
8070     stats.depth = cpstats->depth;
8071     stats.nodes = cpstats->nodes;
8072     stats.score = cpstats->score;
8073     stats.time = cpstats->time;
8074     stats.pv = cpstats->movelist;
8075     stats.hint = lastHint;
8076     stats.an_move_index = 0;
8077     stats.an_move_count = 0;
8078
8079     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8080         stats.hint = cpstats->move_name;
8081         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8082         stats.an_move_count = cpstats->nr_moves;
8083     }
8084
8085     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
8086
8087     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8088         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8089
8090     SetProgramStats( &stats );
8091 }
8092
8093 void
8094 ClearEngineOutputPane (int which)
8095 {
8096     static FrontEndProgramStats dummyStats;
8097     dummyStats.which = which;
8098     dummyStats.pv = "#";
8099     SetProgramStats( &dummyStats );
8100 }
8101
8102 #define MAXPLAYERS 500
8103
8104 char *
8105 TourneyStandings (int display)
8106 {
8107     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8108     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8109     char result, *p, *names[MAXPLAYERS];
8110
8111     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8112         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8113     names[0] = p = strdup(appData.participants);
8114     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8115
8116     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8117
8118     while(result = appData.results[nr]) {
8119         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8120         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8121         wScore = bScore = 0;
8122         switch(result) {
8123           case '+': wScore = 2; break;
8124           case '-': bScore = 2; break;
8125           case '=': wScore = bScore = 1; break;
8126           case ' ':
8127           case '*': return strdup("busy"); // tourney not finished
8128         }
8129         score[w] += wScore;
8130         score[b] += bScore;
8131         games[w]++;
8132         games[b]++;
8133         nr++;
8134     }
8135     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8136     for(w=0; w<nPlayers; w++) {
8137         bScore = -1;
8138         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8139         ranking[w] = b; points[w] = bScore; score[b] = -2;
8140     }
8141     p = malloc(nPlayers*34+1);
8142     for(w=0; w<nPlayers && w<display; w++)
8143         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8144     free(names[0]);
8145     return p;
8146 }
8147
8148 void
8149 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8150 {       // count all piece types
8151         int p, f, r;
8152         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8153         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8154         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8155                 p = board[r][f];
8156                 pCnt[p]++;
8157                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8158                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8159                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8160                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8161                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8162                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8163         }
8164 }
8165
8166 int
8167 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8168 {
8169         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8170         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8171
8172         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8173         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8174         if(myPawns == 2 && nMine == 3) // KPP
8175             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8176         if(myPawns == 1 && nMine == 2) // KP
8177             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8178         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8179             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8180         if(myPawns) return FALSE;
8181         if(pCnt[WhiteRook+side])
8182             return pCnt[BlackRook-side] ||
8183                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8184                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8185                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8186         if(pCnt[WhiteCannon+side]) {
8187             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8188             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8189         }
8190         if(pCnt[WhiteKnight+side])
8191             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8192         return FALSE;
8193 }
8194
8195 int
8196 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8197 {
8198         VariantClass v = gameInfo.variant;
8199
8200         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8201         if(v == VariantShatranj) return TRUE; // always winnable through baring
8202         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8203         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8204
8205         if(v == VariantXiangqi) {
8206                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8207
8208                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8209                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8210                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8211                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8212                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8213                 if(stale) // we have at least one last-rank P plus perhaps C
8214                     return majors // KPKX
8215                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8216                 else // KCA*E*
8217                     return pCnt[WhiteFerz+side] // KCAK
8218                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8219                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8220                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8221
8222         } else if(v == VariantKnightmate) {
8223                 if(nMine == 1) return FALSE;
8224                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8225         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8226                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8227
8228                 if(nMine == 1) return FALSE; // bare King
8229                 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
8230                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8231                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8232                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8233                 if(pCnt[WhiteKnight+side])
8234                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8235                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8236                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8237                 if(nBishops)
8238                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8239                 if(pCnt[WhiteAlfil+side])
8240                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8241                 if(pCnt[WhiteWazir+side])
8242                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8243         }
8244
8245         return TRUE;
8246 }
8247
8248 int
8249 CompareWithRights (Board b1, Board b2)
8250 {
8251     int rights = 0;
8252     if(!CompareBoards(b1, b2)) return FALSE;
8253     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8254     /* compare castling rights */
8255     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8256            rights++; /* King lost rights, while rook still had them */
8257     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8258         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8259            rights++; /* but at least one rook lost them */
8260     }
8261     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8262            rights++;
8263     if( b1[CASTLING][5] != NoRights ) {
8264         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8265            rights++;
8266     }
8267     return rights == 0;
8268 }
8269
8270 int
8271 Adjudicate (ChessProgramState *cps)
8272 {       // [HGM] some adjudications useful with buggy engines
8273         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8274         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8275         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8276         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8277         int k, drop, count = 0; static int bare = 1;
8278         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8279         Boolean canAdjudicate = !appData.icsActive;
8280
8281         // most tests only when we understand the game, i.e. legality-checking on
8282             if( appData.testLegality )
8283             {   /* [HGM] Some more adjudications for obstinate engines */
8284                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8285                 static int moveCount = 6;
8286                 ChessMove result;
8287                 char *reason = NULL;
8288
8289                 /* Count what is on board. */
8290                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8291
8292                 /* Some material-based adjudications that have to be made before stalemate test */
8293                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8294                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8295                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8296                      if(canAdjudicate && appData.checkMates) {
8297                          if(engineOpponent)
8298                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8299                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8300                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8301                          return 1;
8302                      }
8303                 }
8304
8305                 /* Bare King in Shatranj (loses) or Losers (wins) */
8306                 if( nrW == 1 || nrB == 1) {
8307                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8308                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8309                      if(canAdjudicate && appData.checkMates) {
8310                          if(engineOpponent)
8311                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8312                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8313                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8314                          return 1;
8315                      }
8316                   } else
8317                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8318                   {    /* bare King */
8319                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8320                         if(canAdjudicate && appData.checkMates) {
8321                             /* but only adjudicate if adjudication enabled */
8322                             if(engineOpponent)
8323                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8324                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8325                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8326                             return 1;
8327                         }
8328                   }
8329                 } else bare = 1;
8330
8331
8332             // don't wait for engine to announce game end if we can judge ourselves
8333             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8334               case MT_CHECK:
8335                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8336                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8337                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8338                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8339                             checkCnt++;
8340                         if(checkCnt >= 2) {
8341                             reason = "Xboard adjudication: 3rd check";
8342                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8343                             break;
8344                         }
8345                     }
8346                 }
8347               case MT_NONE:
8348               default:
8349                 break;
8350               case MT_STEALMATE:
8351               case MT_STALEMATE:
8352               case MT_STAINMATE:
8353                 reason = "Xboard adjudication: Stalemate";
8354                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8355                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8356                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8357                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8358                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8359                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8360                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8361                                                                         EP_CHECKMATE : EP_WINS);
8362                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8363                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8364                 }
8365                 break;
8366               case MT_CHECKMATE:
8367                 reason = "Xboard adjudication: Checkmate";
8368                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8369                 if(gameInfo.variant == VariantShogi) {
8370                     if(forwardMostMove > backwardMostMove
8371                        && moveList[forwardMostMove-1][1] == '@'
8372                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8373                         reason = "XBoard adjudication: pawn-drop mate";
8374                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8375                     }
8376                 }
8377                 break;
8378             }
8379
8380                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8381                     case EP_STALEMATE:
8382                         result = GameIsDrawn; break;
8383                     case EP_CHECKMATE:
8384                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8385                     case EP_WINS:
8386                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8387                     default:
8388                         result = EndOfFile;
8389                 }
8390                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8391                     if(engineOpponent)
8392                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8393                     GameEnds( result, reason, GE_XBOARD );
8394                     return 1;
8395                 }
8396
8397                 /* Next absolutely insufficient mating material. */
8398                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8399                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8400                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8401
8402                      /* always flag draws, for judging claims */
8403                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8404
8405                      if(canAdjudicate && appData.materialDraws) {
8406                          /* but only adjudicate them if adjudication enabled */
8407                          if(engineOpponent) {
8408                            SendToProgram("force\n", engineOpponent); // suppress reply
8409                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8410                          }
8411                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8412                          return 1;
8413                      }
8414                 }
8415
8416                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8417                 if(gameInfo.variant == VariantXiangqi ?
8418                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8419                  : nrW + nrB == 4 &&
8420                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8421                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8422                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8423                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8424                    ) ) {
8425                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8426                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8427                           if(engineOpponent) {
8428                             SendToProgram("force\n", engineOpponent); // suppress reply
8429                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8430                           }
8431                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8432                           return 1;
8433                      }
8434                 } else moveCount = 6;
8435             }
8436
8437         // Repetition draws and 50-move rule can be applied independently of legality testing
8438
8439                 /* Check for rep-draws */
8440                 count = 0;
8441                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8442                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8443                 for(k = forwardMostMove-2;
8444                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8445                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8446                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8447                     k-=2)
8448                 {   int rights=0;
8449                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8450                         /* compare castling rights */
8451                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8452                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8453                                 rights++; /* King lost rights, while rook still had them */
8454                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8455                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8456                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8457                                    rights++; /* but at least one rook lost them */
8458                         }
8459                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8460                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8461                                 rights++;
8462                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8463                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8464                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8465                                    rights++;
8466                         }
8467                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8468                             && appData.drawRepeats > 1) {
8469                              /* adjudicate after user-specified nr of repeats */
8470                              int result = GameIsDrawn;
8471                              char *details = "XBoard adjudication: repetition draw";
8472                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8473                                 // [HGM] xiangqi: check for forbidden perpetuals
8474                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8475                                 for(m=forwardMostMove; m>k; m-=2) {
8476                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8477                                         ourPerpetual = 0; // the current mover did not always check
8478                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8479                                         hisPerpetual = 0; // the opponent did not always check
8480                                 }
8481                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8482                                                                         ourPerpetual, hisPerpetual);
8483                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8484                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8485                                     details = "Xboard adjudication: perpetual checking";
8486                                 } else
8487                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8488                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8489                                 } else
8490                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8491                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8492                                         result = BlackWins;
8493                                         details = "Xboard adjudication: repetition";
8494                                     }
8495                                 } else // it must be XQ
8496                                 // Now check for perpetual chases
8497                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8498                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8499                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8500                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8501                                         static char resdet[MSG_SIZ];
8502                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8503                                         details = resdet;
8504                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8505                                     } else
8506                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8507                                         break; // Abort repetition-checking loop.
8508                                 }
8509                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8510                              }
8511                              if(engineOpponent) {
8512                                SendToProgram("force\n", engineOpponent); // suppress reply
8513                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8514                              }
8515                              GameEnds( result, details, GE_XBOARD );
8516                              return 1;
8517                         }
8518                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8519                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8520                     }
8521                 }
8522
8523                 /* Now we test for 50-move draws. Determine ply count */
8524                 count = forwardMostMove;
8525                 /* look for last irreversble move */
8526                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8527                     count--;
8528                 /* if we hit starting position, add initial plies */
8529                 if( count == backwardMostMove )
8530                     count -= initialRulePlies;
8531                 count = forwardMostMove - count;
8532                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8533                         // adjust reversible move counter for checks in Xiangqi
8534                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8535                         if(i < backwardMostMove) i = backwardMostMove;
8536                         while(i <= forwardMostMove) {
8537                                 lastCheck = inCheck; // check evasion does not count
8538                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8539                                 if(inCheck || lastCheck) count--; // check does not count
8540                                 i++;
8541                         }
8542                 }
8543                 if( count >= 100)
8544                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8545                          /* this is used to judge if draw claims are legal */
8546                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8547                          if(engineOpponent) {
8548                            SendToProgram("force\n", engineOpponent); // suppress reply
8549                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8550                          }
8551                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8552                          return 1;
8553                 }
8554
8555                 /* if draw offer is pending, treat it as a draw claim
8556                  * when draw condition present, to allow engines a way to
8557                  * claim draws before making their move to avoid a race
8558                  * condition occurring after their move
8559                  */
8560                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8561                          char *p = NULL;
8562                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8563                              p = "Draw claim: 50-move rule";
8564                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8565                              p = "Draw claim: 3-fold repetition";
8566                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8567                              p = "Draw claim: insufficient mating material";
8568                          if( p != NULL && canAdjudicate) {
8569                              if(engineOpponent) {
8570                                SendToProgram("force\n", engineOpponent); // suppress reply
8571                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8572                              }
8573                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8574                              return 1;
8575                          }
8576                 }
8577
8578                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8579                     if(engineOpponent) {
8580                       SendToProgram("force\n", engineOpponent); // suppress reply
8581                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8582                     }
8583                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8584                     return 1;
8585                 }
8586         return 0;
8587 }
8588
8589 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8590 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8591 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8592
8593 static int
8594 BitbaseProbe ()
8595 {
8596     int pieces[10], squares[10], cnt=0, r, f, res;
8597     static int loaded;
8598     static PPROBE_EGBB probeBB;
8599     if(!appData.testLegality) return 10;
8600     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8601     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8602     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8603     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8604         ChessSquare piece = boards[forwardMostMove][r][f];
8605         int black = (piece >= BlackPawn);
8606         int type = piece - black*BlackPawn;
8607         if(piece == EmptySquare) continue;
8608         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8609         if(type == WhiteKing) type = WhiteQueen + 1;
8610         type = egbbCode[type];
8611         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8612         pieces[cnt] = type + black*6;
8613         if(++cnt > 5) return 11;
8614     }
8615     pieces[cnt] = squares[cnt] = 0;
8616     // probe EGBB
8617     if(loaded == 2) return 13; // loading failed before
8618     if(loaded == 0) {
8619         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8620         HMODULE lib;
8621         PLOAD_EGBB loadBB;
8622         loaded = 2; // prepare for failure
8623         if(!path) return 13; // no egbb installed
8624         strncpy(buf, path + 8, MSG_SIZ);
8625         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8626         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8627         lib = LoadLibrary(buf);
8628         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8629         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8630         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8631         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8632         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8633         loaded = 1; // success!
8634     }
8635     res = probeBB(forwardMostMove & 1, pieces, squares);
8636     return res > 0 ? 1 : res < 0 ? -1 : 0;
8637 }
8638
8639 char *
8640 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8641 {   // [HGM] book: this routine intercepts moves to simulate book replies
8642     char *bookHit = NULL;
8643
8644     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8645         char buf[MSG_SIZ];
8646         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8647         SendToProgram(buf, cps);
8648     }
8649     //first determine if the incoming move brings opponent into his book
8650     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8651         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8652     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8653     if(bookHit != NULL && !cps->bookSuspend) {
8654         // make sure opponent is not going to reply after receiving move to book position
8655         SendToProgram("force\n", cps);
8656         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8657     }
8658     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8659     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8660     // now arrange restart after book miss
8661     if(bookHit) {
8662         // after a book hit we never send 'go', and the code after the call to this routine
8663         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8664         char buf[MSG_SIZ], *move = bookHit;
8665         if(cps->useSAN) {
8666             int fromX, fromY, toX, toY;
8667             char promoChar;
8668             ChessMove moveType;
8669             move = buf + 30;
8670             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8671                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8672                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8673                                     PosFlags(forwardMostMove),
8674                                     fromY, fromX, toY, toX, promoChar, move);
8675             } else {
8676                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8677                 bookHit = NULL;
8678             }
8679         }
8680         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8681         SendToProgram(buf, cps);
8682         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8683     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8684         SendToProgram("go\n", cps);
8685         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8686     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8687         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8688             SendToProgram("go\n", cps);
8689         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8690     }
8691     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8692 }
8693
8694 int
8695 LoadError (char *errmess, ChessProgramState *cps)
8696 {   // unloads engine and switches back to -ncp mode if it was first
8697     if(cps->initDone) return FALSE;
8698     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8699     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8700     cps->pr = NoProc;
8701     if(cps == &first) {
8702         appData.noChessProgram = TRUE;
8703         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8704         gameMode = BeginningOfGame; ModeHighlight();
8705         SetNCPMode();
8706     }
8707     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8708     DisplayMessage("", ""); // erase waiting message
8709     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8710     return TRUE;
8711 }
8712
8713 char *savedMessage;
8714 ChessProgramState *savedState;
8715 void
8716 DeferredBookMove (void)
8717 {
8718         if(savedState->lastPing != savedState->lastPong)
8719                     ScheduleDelayedEvent(DeferredBookMove, 10);
8720         else
8721         HandleMachineMove(savedMessage, savedState);
8722 }
8723
8724 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8725 static ChessProgramState *stalledEngine;
8726 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8727
8728 void
8729 HandleMachineMove (char *message, ChessProgramState *cps)
8730 {
8731     static char firstLeg[20], legs;
8732     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8733     char realname[MSG_SIZ];
8734     int fromX, fromY, toX, toY;
8735     ChessMove moveType;
8736     char promoChar, roar;
8737     char *p, *pv=buf1;
8738     int oldError;
8739     char *bookHit;
8740
8741     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8742         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8743         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8744             DisplayError(_("Invalid pairing from pairing engine"), 0);
8745             return;
8746         }
8747         pairingReceived = 1;
8748         NextMatchGame();
8749         return; // Skim the pairing messages here.
8750     }
8751
8752     oldError = cps->userError; cps->userError = 0;
8753
8754 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8755     /*
8756      * Kludge to ignore BEL characters
8757      */
8758     while (*message == '\007') message++;
8759
8760     /*
8761      * [HGM] engine debug message: ignore lines starting with '#' character
8762      */
8763     if(cps->debug && *message == '#') return;
8764
8765     /*
8766      * Look for book output
8767      */
8768     if (cps == &first && bookRequested) {
8769         if (message[0] == '\t' || message[0] == ' ') {
8770             /* Part of the book output is here; append it */
8771             strcat(bookOutput, message);
8772             strcat(bookOutput, "  \n");
8773             return;
8774         } else if (bookOutput[0] != NULLCHAR) {
8775             /* All of book output has arrived; display it */
8776             char *p = bookOutput;
8777             while (*p != NULLCHAR) {
8778                 if (*p == '\t') *p = ' ';
8779                 p++;
8780             }
8781             DisplayInformation(bookOutput);
8782             bookRequested = FALSE;
8783             /* Fall through to parse the current output */
8784         }
8785     }
8786
8787     /*
8788      * Look for machine move.
8789      */
8790     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8791         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8792     {
8793         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8794             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8795             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8796             stalledEngine = cps;
8797             if(appData.ponderNextMove) { // bring opponent out of ponder
8798                 if(gameMode == TwoMachinesPlay) {
8799                     if(cps->other->pause)
8800                         PauseEngine(cps->other);
8801                     else
8802                         SendToProgram("easy\n", cps->other);
8803                 }
8804             }
8805             StopClocks();
8806             return;
8807         }
8808
8809       if(cps->usePing) {
8810
8811         /* This method is only useful on engines that support ping */
8812         if(abortEngineThink) {
8813             if (appData.debugMode) {
8814                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8815             }
8816             SendToProgram("undo\n", cps);
8817             return;
8818         }
8819
8820         if (cps->lastPing != cps->lastPong) {
8821             /* Extra move from before last new; ignore */
8822             if (appData.debugMode) {
8823                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8824             }
8825           return;
8826         }
8827
8828       } else {
8829
8830         int machineWhite = FALSE;
8831
8832         switch (gameMode) {
8833           case BeginningOfGame:
8834             /* Extra move from before last reset; ignore */
8835             if (appData.debugMode) {
8836                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8837             }
8838             return;
8839
8840           case EndOfGame:
8841           case IcsIdle:
8842           default:
8843             /* Extra move after we tried to stop.  The mode test is
8844                not a reliable way of detecting this problem, but it's
8845                the best we can do on engines that don't support ping.
8846             */
8847             if (appData.debugMode) {
8848                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8849                         cps->which, gameMode);
8850             }
8851             SendToProgram("undo\n", cps);
8852             return;
8853
8854           case MachinePlaysWhite:
8855           case IcsPlayingWhite:
8856             machineWhite = TRUE;
8857             break;
8858
8859           case MachinePlaysBlack:
8860           case IcsPlayingBlack:
8861             machineWhite = FALSE;
8862             break;
8863
8864           case TwoMachinesPlay:
8865             machineWhite = (cps->twoMachinesColor[0] == 'w');
8866             break;
8867         }
8868         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8869             if (appData.debugMode) {
8870                 fprintf(debugFP,
8871                         "Ignoring move out of turn by %s, gameMode %d"
8872                         ", forwardMost %d\n",
8873                         cps->which, gameMode, forwardMostMove);
8874             }
8875             return;
8876         }
8877       }
8878
8879         if(cps->alphaRank) AlphaRank(machineMove, 4);
8880
8881         // [HGM] lion: (some very limited) support for Alien protocol
8882         killX = killY = kill2X = kill2Y = -1;
8883         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8884             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8885             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8886             return;
8887         }
8888         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8889             char *q = strchr(p+1, ',');            // second comma?
8890             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8891             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8892             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8893         }
8894         if(firstLeg[0]) { // there was a previous leg;
8895             // only support case where same piece makes two step
8896             char buf[20], *p = machineMove+1, *q = buf+1, f;
8897             safeStrCpy(buf, machineMove, 20);
8898             while(isdigit(*q)) q++; // find start of to-square
8899             safeStrCpy(machineMove, firstLeg, 20);
8900             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8901             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
8902             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)
8903             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8904             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8905             firstLeg[0] = NULLCHAR; legs = 0;
8906         }
8907
8908         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8909                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8910             /* Machine move could not be parsed; ignore it. */
8911           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8912                     machineMove, _(cps->which));
8913             DisplayMoveError(buf1);
8914             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8915                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8916             if (gameMode == TwoMachinesPlay) {
8917               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8918                        buf1, GE_XBOARD);
8919             }
8920             return;
8921         }
8922
8923         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8924         /* So we have to redo legality test with true e.p. status here,  */
8925         /* to make sure an illegal e.p. capture does not slip through,   */
8926         /* to cause a forfeit on a justified illegal-move complaint      */
8927         /* of the opponent.                                              */
8928         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8929            ChessMove moveType;
8930            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8931                              fromY, fromX, toY, toX, promoChar);
8932             if(moveType == IllegalMove) {
8933               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8934                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8935                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8936                            buf1, GE_XBOARD);
8937                 return;
8938            } else if(!appData.fischerCastling)
8939            /* [HGM] Kludge to handle engines that send FRC-style castling
8940               when they shouldn't (like TSCP-Gothic) */
8941            switch(moveType) {
8942              case WhiteASideCastleFR:
8943              case BlackASideCastleFR:
8944                toX+=2;
8945                currentMoveString[2]++;
8946                break;
8947              case WhiteHSideCastleFR:
8948              case BlackHSideCastleFR:
8949                toX--;
8950                currentMoveString[2]--;
8951                break;
8952              default: ; // nothing to do, but suppresses warning of pedantic compilers
8953            }
8954         }
8955         hintRequested = FALSE;
8956         lastHint[0] = NULLCHAR;
8957         bookRequested = FALSE;
8958         /* Program may be pondering now */
8959         cps->maybeThinking = TRUE;
8960         if (cps->sendTime == 2) cps->sendTime = 1;
8961         if (cps->offeredDraw) cps->offeredDraw--;
8962
8963         /* [AS] Save move info*/
8964         pvInfoList[ forwardMostMove ].score = programStats.score;
8965         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8966         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8967
8968         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8969
8970         /* Test suites abort the 'game' after one move */
8971         if(*appData.finger) {
8972            static FILE *f;
8973            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8974            if(!f) f = fopen(appData.finger, "w");
8975            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8976            else { DisplayFatalError("Bad output file", errno, 0); return; }
8977            free(fen);
8978            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8979         }
8980         if(appData.epd) {
8981            if(solvingTime >= 0) {
8982               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8983               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8984            } else {
8985               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8986               if(solvingTime == -2) second.matchWins++;
8987            }
8988            OutputKibitz(2, buf1);
8989            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8990         }
8991
8992         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8993         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8994             int count = 0;
8995
8996             while( count < adjudicateLossPlies ) {
8997                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8998
8999                 if( count & 1 ) {
9000                     score = -score; /* Flip score for winning side */
9001                 }
9002
9003                 if( score > appData.adjudicateLossThreshold ) {
9004                     break;
9005                 }
9006
9007                 count++;
9008             }
9009
9010             if( count >= adjudicateLossPlies ) {
9011                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9012
9013                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9014                     "Xboard adjudication",
9015                     GE_XBOARD );
9016
9017                 return;
9018             }
9019         }
9020
9021         if(Adjudicate(cps)) {
9022             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9023             return; // [HGM] adjudicate: for all automatic game ends
9024         }
9025
9026 #if ZIPPY
9027         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9028             first.initDone) {
9029           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9030                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9031                 SendToICS("draw ");
9032                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9033           }
9034           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9035           ics_user_moved = 1;
9036           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9037                 char buf[3*MSG_SIZ];
9038
9039                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9040                         programStats.score / 100.,
9041                         programStats.depth,
9042                         programStats.time / 100.,
9043                         (unsigned int)programStats.nodes,
9044                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9045                         programStats.movelist);
9046                 SendToICS(buf);
9047           }
9048         }
9049 #endif
9050
9051         /* [AS] Clear stats for next move */
9052         ClearProgramStats();
9053         thinkOutput[0] = NULLCHAR;
9054         hiddenThinkOutputState = 0;
9055
9056         bookHit = NULL;
9057         if (gameMode == TwoMachinesPlay) {
9058             /* [HGM] relaying draw offers moved to after reception of move */
9059             /* and interpreting offer as claim if it brings draw condition */
9060             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9061                 SendToProgram("draw\n", cps->other);
9062             }
9063             if (cps->other->sendTime) {
9064                 SendTimeRemaining(cps->other,
9065                                   cps->other->twoMachinesColor[0] == 'w');
9066             }
9067             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9068             if (firstMove && !bookHit) {
9069                 firstMove = FALSE;
9070                 if (cps->other->useColors) {
9071                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9072                 }
9073                 SendToProgram("go\n", cps->other);
9074             }
9075             cps->other->maybeThinking = TRUE;
9076         }
9077
9078         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9079
9080         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9081
9082         if (!pausing && appData.ringBellAfterMoves) {
9083             if(!roar) RingBell();
9084         }
9085
9086         /*
9087          * Reenable menu items that were disabled while
9088          * machine was thinking
9089          */
9090         if (gameMode != TwoMachinesPlay)
9091             SetUserThinkingEnables();
9092
9093         // [HGM] book: after book hit opponent has received move and is now in force mode
9094         // force the book reply into it, and then fake that it outputted this move by jumping
9095         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9096         if(bookHit) {
9097                 static char bookMove[MSG_SIZ]; // a bit generous?
9098
9099                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9100                 strcat(bookMove, bookHit);
9101                 message = bookMove;
9102                 cps = cps->other;
9103                 programStats.nodes = programStats.depth = programStats.time =
9104                 programStats.score = programStats.got_only_move = 0;
9105                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9106
9107                 if(cps->lastPing != cps->lastPong) {
9108                     savedMessage = message; // args for deferred call
9109                     savedState = cps;
9110                     ScheduleDelayedEvent(DeferredBookMove, 10);
9111                     return;
9112                 }
9113                 goto FakeBookMove;
9114         }
9115
9116         return;
9117     }
9118
9119     /* Set special modes for chess engines.  Later something general
9120      *  could be added here; for now there is just one kludge feature,
9121      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9122      *  when "xboard" is given as an interactive command.
9123      */
9124     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9125         cps->useSigint = FALSE;
9126         cps->useSigterm = FALSE;
9127     }
9128     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9129       ParseFeatures(message+8, cps);
9130       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9131     }
9132
9133     if (!strncmp(message, "setup ", 6) && 
9134         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9135           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9136                                         ) { // [HGM] allow first engine to define opening position
9137       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9138       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9139       *buf = NULLCHAR;
9140       if(sscanf(message, "setup (%s", buf) == 1) {
9141         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9142         ASSIGN(appData.pieceToCharTable, buf);
9143       }
9144       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9145       if(dummy >= 3) {
9146         while(message[s] && message[s++] != ' ');
9147         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9148            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9149             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9150             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9151             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9152           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9153           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9154           startedFromSetupPosition = FALSE;
9155         }
9156       }
9157       if(startedFromSetupPosition) return;
9158       ParseFEN(boards[0], &dummy, message+s, FALSE);
9159       DrawPosition(TRUE, boards[0]);
9160       CopyBoard(initialPosition, boards[0]);
9161       startedFromSetupPosition = TRUE;
9162       return;
9163     }
9164     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9165       ChessSquare piece = WhitePawn;
9166       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9167       if(*p == '+') promoted++, ID = *++p;
9168       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9169       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9170       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9171       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9172       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9173       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9174       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9175       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9176                                                && gameInfo.variant != VariantGreat
9177                                                && gameInfo.variant != VariantFairy    ) return;
9178       if(piece < EmptySquare) {
9179         pieceDefs = TRUE;
9180         ASSIGN(pieceDesc[piece], buf1);
9181         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9182       }
9183       return;
9184     }
9185     if(sscanf(message, "choice %s", promoRestrict) == 1) {
9186       if(deferChoice) {
9187         LeftClick(Press, 0, 0); // finish the click that was interrupted
9188       } else if(promoSweep != EmptySquare) {
9189         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9190         if(strlen(promoRestrict) > 1) Sweep(0);
9191       }
9192       return;
9193     }
9194     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9195      * want this, I was asked to put it in, and obliged.
9196      */
9197     if (!strncmp(message, "setboard ", 9)) {
9198         Board initial_position;
9199
9200         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9201
9202         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9203             DisplayError(_("Bad FEN received from engine"), 0);
9204             return ;
9205         } else {
9206            Reset(TRUE, FALSE);
9207            CopyBoard(boards[0], initial_position);
9208            initialRulePlies = FENrulePlies;
9209            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9210            else gameMode = MachinePlaysBlack;
9211            DrawPosition(FALSE, boards[currentMove]);
9212         }
9213         return;
9214     }
9215
9216     /*
9217      * Look for communication commands
9218      */
9219     if (!strncmp(message, "telluser ", 9)) {
9220         if(message[9] == '\\' && message[10] == '\\')
9221             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9222         PlayTellSound();
9223         DisplayNote(message + 9);
9224         return;
9225     }
9226     if (!strncmp(message, "tellusererror ", 14)) {
9227         cps->userError = 1;
9228         if(message[14] == '\\' && message[15] == '\\')
9229             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9230         PlayTellSound();
9231         DisplayError(message + 14, 0);
9232         return;
9233     }
9234     if (!strncmp(message, "tellopponent ", 13)) {
9235       if (appData.icsActive) {
9236         if (loggedOn) {
9237           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9238           SendToICS(buf1);
9239         }
9240       } else {
9241         DisplayNote(message + 13);
9242       }
9243       return;
9244     }
9245     if (!strncmp(message, "tellothers ", 11)) {
9246       if (appData.icsActive) {
9247         if (loggedOn) {
9248           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9249           SendToICS(buf1);
9250         }
9251       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9252       return;
9253     }
9254     if (!strncmp(message, "tellall ", 8)) {
9255       if (appData.icsActive) {
9256         if (loggedOn) {
9257           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9258           SendToICS(buf1);
9259         }
9260       } else {
9261         DisplayNote(message + 8);
9262       }
9263       return;
9264     }
9265     if (strncmp(message, "warning", 7) == 0) {
9266         /* Undocumented feature, use tellusererror in new code */
9267         DisplayError(message, 0);
9268         return;
9269     }
9270     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9271         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9272         strcat(realname, " query");
9273         AskQuestion(realname, buf2, buf1, cps->pr);
9274         return;
9275     }
9276     /* Commands from the engine directly to ICS.  We don't allow these to be
9277      *  sent until we are logged on. Crafty kibitzes have been known to
9278      *  interfere with the login process.
9279      */
9280     if (loggedOn) {
9281         if (!strncmp(message, "tellics ", 8)) {
9282             SendToICS(message + 8);
9283             SendToICS("\n");
9284             return;
9285         }
9286         if (!strncmp(message, "tellicsnoalias ", 15)) {
9287             SendToICS(ics_prefix);
9288             SendToICS(message + 15);
9289             SendToICS("\n");
9290             return;
9291         }
9292         /* The following are for backward compatibility only */
9293         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9294             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9295             SendToICS(ics_prefix);
9296             SendToICS(message);
9297             SendToICS("\n");
9298             return;
9299         }
9300     }
9301     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9302         if(initPing == cps->lastPong) {
9303             if(gameInfo.variant == VariantUnknown) {
9304                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9305                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9306                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9307             }
9308             initPing = -1;
9309         }
9310         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9311             abortEngineThink = FALSE;
9312             DisplayMessage("", "");
9313             ThawUI();
9314         }
9315         return;
9316     }
9317     if(!strncmp(message, "highlight ", 10)) {
9318         if(appData.testLegality && !*engineVariant && appData.markers) return;
9319         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9320         return;
9321     }
9322     if(!strncmp(message, "click ", 6)) {
9323         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9324         if(appData.testLegality || !appData.oneClick) return;
9325         sscanf(message+6, "%c%d%c", &f, &y, &c);
9326         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9327         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9328         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9329         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9330         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9331         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9332             LeftClick(Release, lastLeftX, lastLeftY);
9333         controlKey  = (c == ',');
9334         LeftClick(Press, x, y);
9335         LeftClick(Release, x, y);
9336         first.highlight = f;
9337         return;
9338     }
9339     /*
9340      * If the move is illegal, cancel it and redraw the board.
9341      * Also deal with other error cases.  Matching is rather loose
9342      * here to accommodate engines written before the spec.
9343      */
9344     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9345         strncmp(message, "Error", 5) == 0) {
9346         if (StrStr(message, "name") ||
9347             StrStr(message, "rating") || StrStr(message, "?") ||
9348             StrStr(message, "result") || StrStr(message, "board") ||
9349             StrStr(message, "bk") || StrStr(message, "computer") ||
9350             StrStr(message, "variant") || StrStr(message, "hint") ||
9351             StrStr(message, "random") || StrStr(message, "depth") ||
9352             StrStr(message, "accepted")) {
9353             return;
9354         }
9355         if (StrStr(message, "protover")) {
9356           /* Program is responding to input, so it's apparently done
9357              initializing, and this error message indicates it is
9358              protocol version 1.  So we don't need to wait any longer
9359              for it to initialize and send feature commands. */
9360           FeatureDone(cps, 1);
9361           cps->protocolVersion = 1;
9362           return;
9363         }
9364         cps->maybeThinking = FALSE;
9365
9366         if (StrStr(message, "draw")) {
9367             /* Program doesn't have "draw" command */
9368             cps->sendDrawOffers = 0;
9369             return;
9370         }
9371         if (cps->sendTime != 1 &&
9372             (StrStr(message, "time") || StrStr(message, "otim"))) {
9373           /* Program apparently doesn't have "time" or "otim" command */
9374           cps->sendTime = 0;
9375           return;
9376         }
9377         if (StrStr(message, "analyze")) {
9378             cps->analysisSupport = FALSE;
9379             cps->analyzing = FALSE;
9380 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9381             EditGameEvent(); // [HGM] try to preserve loaded game
9382             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9383             DisplayError(buf2, 0);
9384             return;
9385         }
9386         if (StrStr(message, "(no matching move)st")) {
9387           /* Special kludge for GNU Chess 4 only */
9388           cps->stKludge = TRUE;
9389           SendTimeControl(cps, movesPerSession, timeControl,
9390                           timeIncrement, appData.searchDepth,
9391                           searchTime);
9392           return;
9393         }
9394         if (StrStr(message, "(no matching move)sd")) {
9395           /* Special kludge for GNU Chess 4 only */
9396           cps->sdKludge = TRUE;
9397           SendTimeControl(cps, movesPerSession, timeControl,
9398                           timeIncrement, appData.searchDepth,
9399                           searchTime);
9400           return;
9401         }
9402         if (!StrStr(message, "llegal")) {
9403             return;
9404         }
9405         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9406             gameMode == IcsIdle) return;
9407         if (forwardMostMove <= backwardMostMove) return;
9408         if (pausing) PauseEvent();
9409       if(appData.forceIllegal) {
9410             // [HGM] illegal: machine refused move; force position after move into it
9411           SendToProgram("force\n", cps);
9412           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9413                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9414                 // when black is to move, while there might be nothing on a2 or black
9415                 // might already have the move. So send the board as if white has the move.
9416                 // But first we must change the stm of the engine, as it refused the last move
9417                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9418                 if(WhiteOnMove(forwardMostMove)) {
9419                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9420                     SendBoard(cps, forwardMostMove); // kludgeless board
9421                 } else {
9422                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9423                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9424                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9425                 }
9426           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9427             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9428                  gameMode == TwoMachinesPlay)
9429               SendToProgram("go\n", cps);
9430             return;
9431       } else
9432         if (gameMode == PlayFromGameFile) {
9433             /* Stop reading this game file */
9434             gameMode = EditGame;
9435             ModeHighlight();
9436         }
9437         /* [HGM] illegal-move claim should forfeit game when Xboard */
9438         /* only passes fully legal moves                            */
9439         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9440             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9441                                 "False illegal-move claim", GE_XBOARD );
9442             return; // do not take back move we tested as valid
9443         }
9444         currentMove = forwardMostMove-1;
9445         DisplayMove(currentMove-1); /* before DisplayMoveError */
9446         SwitchClocks(forwardMostMove-1); // [HGM] race
9447         DisplayBothClocks();
9448         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9449                 parseList[currentMove], _(cps->which));
9450         DisplayMoveError(buf1);
9451         DrawPosition(FALSE, boards[currentMove]);
9452
9453         SetUserThinkingEnables();
9454         return;
9455     }
9456     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9457         /* Program has a broken "time" command that
9458            outputs a string not ending in newline.
9459            Don't use it. */
9460         cps->sendTime = 0;
9461     }
9462     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9463         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9464             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9465     }
9466
9467     /*
9468      * If chess program startup fails, exit with an error message.
9469      * Attempts to recover here are futile. [HGM] Well, we try anyway
9470      */
9471     if ((StrStr(message, "unknown host") != NULL)
9472         || (StrStr(message, "No remote directory") != NULL)
9473         || (StrStr(message, "not found") != NULL)
9474         || (StrStr(message, "No such file") != NULL)
9475         || (StrStr(message, "can't alloc") != NULL)
9476         || (StrStr(message, "Permission denied") != NULL)) {
9477
9478         cps->maybeThinking = FALSE;
9479         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9480                 _(cps->which), cps->program, cps->host, message);
9481         RemoveInputSource(cps->isr);
9482         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9483             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9484             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9485         }
9486         return;
9487     }
9488
9489     /*
9490      * Look for hint output
9491      */
9492     if (sscanf(message, "Hint: %s", buf1) == 1) {
9493         if (cps == &first && hintRequested) {
9494             hintRequested = FALSE;
9495             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9496                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9497                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9498                                     PosFlags(forwardMostMove),
9499                                     fromY, fromX, toY, toX, promoChar, buf1);
9500                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9501                 DisplayInformation(buf2);
9502             } else {
9503                 /* Hint move could not be parsed!? */
9504               snprintf(buf2, sizeof(buf2),
9505                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9506                         buf1, _(cps->which));
9507                 DisplayError(buf2, 0);
9508             }
9509         } else {
9510           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9511         }
9512         return;
9513     }
9514
9515     /*
9516      * Ignore other messages if game is not in progress
9517      */
9518     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9519         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9520
9521     /*
9522      * look for win, lose, draw, or draw offer
9523      */
9524     if (strncmp(message, "1-0", 3) == 0) {
9525         char *p, *q, *r = "";
9526         p = strchr(message, '{');
9527         if (p) {
9528             q = strchr(p, '}');
9529             if (q) {
9530                 *q = NULLCHAR;
9531                 r = p + 1;
9532             }
9533         }
9534         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9535         return;
9536     } else if (strncmp(message, "0-1", 3) == 0) {
9537         char *p, *q, *r = "";
9538         p = strchr(message, '{');
9539         if (p) {
9540             q = strchr(p, '}');
9541             if (q) {
9542                 *q = NULLCHAR;
9543                 r = p + 1;
9544             }
9545         }
9546         /* Kludge for Arasan 4.1 bug */
9547         if (strcmp(r, "Black resigns") == 0) {
9548             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9549             return;
9550         }
9551         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9552         return;
9553     } else if (strncmp(message, "1/2", 3) == 0) {
9554         char *p, *q, *r = "";
9555         p = strchr(message, '{');
9556         if (p) {
9557             q = strchr(p, '}');
9558             if (q) {
9559                 *q = NULLCHAR;
9560                 r = p + 1;
9561             }
9562         }
9563
9564         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9565         return;
9566
9567     } else if (strncmp(message, "White resign", 12) == 0) {
9568         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9569         return;
9570     } else if (strncmp(message, "Black resign", 12) == 0) {
9571         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9572         return;
9573     } else if (strncmp(message, "White matches", 13) == 0 ||
9574                strncmp(message, "Black matches", 13) == 0   ) {
9575         /* [HGM] ignore GNUShogi noises */
9576         return;
9577     } else if (strncmp(message, "White", 5) == 0 &&
9578                message[5] != '(' &&
9579                StrStr(message, "Black") == NULL) {
9580         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9581         return;
9582     } else if (strncmp(message, "Black", 5) == 0 &&
9583                message[5] != '(') {
9584         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9585         return;
9586     } else if (strcmp(message, "resign") == 0 ||
9587                strcmp(message, "computer resigns") == 0) {
9588         switch (gameMode) {
9589           case MachinePlaysBlack:
9590           case IcsPlayingBlack:
9591             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9592             break;
9593           case MachinePlaysWhite:
9594           case IcsPlayingWhite:
9595             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9596             break;
9597           case TwoMachinesPlay:
9598             if (cps->twoMachinesColor[0] == 'w')
9599               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9600             else
9601               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9602             break;
9603           default:
9604             /* can't happen */
9605             break;
9606         }
9607         return;
9608     } else if (strncmp(message, "opponent mates", 14) == 0) {
9609         switch (gameMode) {
9610           case MachinePlaysBlack:
9611           case IcsPlayingBlack:
9612             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9613             break;
9614           case MachinePlaysWhite:
9615           case IcsPlayingWhite:
9616             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9617             break;
9618           case TwoMachinesPlay:
9619             if (cps->twoMachinesColor[0] == 'w')
9620               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9621             else
9622               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9623             break;
9624           default:
9625             /* can't happen */
9626             break;
9627         }
9628         return;
9629     } else if (strncmp(message, "computer mates", 14) == 0) {
9630         switch (gameMode) {
9631           case MachinePlaysBlack:
9632           case IcsPlayingBlack:
9633             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9634             break;
9635           case MachinePlaysWhite:
9636           case IcsPlayingWhite:
9637             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9638             break;
9639           case TwoMachinesPlay:
9640             if (cps->twoMachinesColor[0] == 'w')
9641               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9642             else
9643               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9644             break;
9645           default:
9646             /* can't happen */
9647             break;
9648         }
9649         return;
9650     } else if (strncmp(message, "checkmate", 9) == 0) {
9651         if (WhiteOnMove(forwardMostMove)) {
9652             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9653         } else {
9654             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9655         }
9656         return;
9657     } else if (strstr(message, "Draw") != NULL ||
9658                strstr(message, "game is a draw") != NULL) {
9659         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9660         return;
9661     } else if (strstr(message, "offer") != NULL &&
9662                strstr(message, "draw") != NULL) {
9663 #if ZIPPY
9664         if (appData.zippyPlay && first.initDone) {
9665             /* Relay offer to ICS */
9666             SendToICS(ics_prefix);
9667             SendToICS("draw\n");
9668         }
9669 #endif
9670         cps->offeredDraw = 2; /* valid until this engine moves twice */
9671         if (gameMode == TwoMachinesPlay) {
9672             if (cps->other->offeredDraw) {
9673                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9674             /* [HGM] in two-machine mode we delay relaying draw offer      */
9675             /* until after we also have move, to see if it is really claim */
9676             }
9677         } else if (gameMode == MachinePlaysWhite ||
9678                    gameMode == MachinePlaysBlack) {
9679           if (userOfferedDraw) {
9680             DisplayInformation(_("Machine accepts your draw offer"));
9681             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9682           } else {
9683             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9684           }
9685         }
9686     }
9687
9688
9689     /*
9690      * Look for thinking output
9691      */
9692     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9693           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9694                                 ) {
9695         int plylev, mvleft, mvtot, curscore, time;
9696         char mvname[MOVE_LEN];
9697         u64 nodes; // [DM]
9698         char plyext;
9699         int ignore = FALSE;
9700         int prefixHint = FALSE;
9701         mvname[0] = NULLCHAR;
9702
9703         switch (gameMode) {
9704           case MachinePlaysBlack:
9705           case IcsPlayingBlack:
9706             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9707             break;
9708           case MachinePlaysWhite:
9709           case IcsPlayingWhite:
9710             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9711             break;
9712           case AnalyzeMode:
9713           case AnalyzeFile:
9714             break;
9715           case IcsObserving: /* [DM] icsEngineAnalyze */
9716             if (!appData.icsEngineAnalyze) ignore = TRUE;
9717             break;
9718           case TwoMachinesPlay:
9719             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9720                 ignore = TRUE;
9721             }
9722             break;
9723           default:
9724             ignore = TRUE;
9725             break;
9726         }
9727
9728         if (!ignore) {
9729             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9730             int solved = 0;
9731             buf1[0] = NULLCHAR;
9732             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9733                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9734                 char score_buf[MSG_SIZ];
9735
9736                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9737                     nodes += u64Const(0x100000000);
9738
9739                 if (plyext != ' ' && plyext != '\t') {
9740                     time *= 100;
9741                 }
9742
9743                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9744                 if( cps->scoreIsAbsolute &&
9745                     ( gameMode == MachinePlaysBlack ||
9746                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9747                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9748                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9749                      !WhiteOnMove(currentMove)
9750                     ) )
9751                 {
9752                     curscore = -curscore;
9753                 }
9754
9755                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9756
9757                 if(*bestMove) { // rememer time best EPD move was first found
9758                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9759                     ChessMove mt; char *p = bestMove;
9760                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9761                     solved = 0;
9762                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9763                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9764                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9765                             solved = 1;
9766                             break;
9767                         }
9768                         while(*p && *p != ' ') p++;
9769                         while(*p == ' ') p++;
9770                     }
9771                     if(!solved) solvingTime = -1;
9772                 }
9773                 if(*avoidMove && !solved) {
9774                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9775                     ChessMove mt; char *p = avoidMove, solved = 1;
9776                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9777                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9778                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9779                             solved = 0; solvingTime = -2;
9780                             break;
9781                         }
9782                         while(*p && *p != ' ') p++;
9783                         while(*p == ' ') p++;
9784                     }
9785                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9786                 }
9787
9788                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9789                         char buf[MSG_SIZ];
9790                         FILE *f;
9791                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9792                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9793                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9794                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9795                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9796                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9797                                 fclose(f);
9798                         }
9799                         else
9800                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9801                           DisplayError(_("failed writing PV"), 0);
9802                 }
9803
9804                 tempStats.depth = plylev;
9805                 tempStats.nodes = nodes;
9806                 tempStats.time = time;
9807                 tempStats.score = curscore;
9808                 tempStats.got_only_move = 0;
9809
9810                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9811                         int ticklen;
9812
9813                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9814                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9815                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9816                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9817                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9818                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9819                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9820                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9821                 }
9822
9823                 /* Buffer overflow protection */
9824                 if (pv[0] != NULLCHAR) {
9825                     if (strlen(pv) >= sizeof(tempStats.movelist)
9826                         && appData.debugMode) {
9827                         fprintf(debugFP,
9828                                 "PV is too long; using the first %u bytes.\n",
9829                                 (unsigned) sizeof(tempStats.movelist) - 1);
9830                     }
9831
9832                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9833                 } else {
9834                     sprintf(tempStats.movelist, " no PV\n");
9835                 }
9836
9837                 if (tempStats.seen_stat) {
9838                     tempStats.ok_to_send = 1;
9839                 }
9840
9841                 if (strchr(tempStats.movelist, '(') != NULL) {
9842                     tempStats.line_is_book = 1;
9843                     tempStats.nr_moves = 0;
9844                     tempStats.moves_left = 0;
9845                 } else {
9846                     tempStats.line_is_book = 0;
9847                 }
9848
9849                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9850                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9851
9852                 SendProgramStatsToFrontend( cps, &tempStats );
9853
9854                 /*
9855                     [AS] Protect the thinkOutput buffer from overflow... this
9856                     is only useful if buf1 hasn't overflowed first!
9857                 */
9858                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9859                 if(curscore >= MATE_SCORE) 
9860                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9861                 else if(curscore <= -MATE_SCORE) 
9862                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9863                 else
9864                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9865                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9866                          plylev,
9867                          (gameMode == TwoMachinesPlay ?
9868                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9869                          score_buf,
9870                          prefixHint ? lastHint : "",
9871                          prefixHint ? " " : "" );
9872
9873                 if( buf1[0] != NULLCHAR ) {
9874                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9875
9876                     if( strlen(pv) > max_len ) {
9877                         if( appData.debugMode) {
9878                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9879                         }
9880                         pv[max_len+1] = '\0';
9881                     }
9882
9883                     strcat( thinkOutput, pv);
9884                 }
9885
9886                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9887                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9888                     DisplayMove(currentMove - 1);
9889                 }
9890                 return;
9891
9892             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9893                 /* crafty (9.25+) says "(only move) <move>"
9894                  * if there is only 1 legal move
9895                  */
9896                 sscanf(p, "(only move) %s", buf1);
9897                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9898                 sprintf(programStats.movelist, "%s (only move)", buf1);
9899                 programStats.depth = 1;
9900                 programStats.nr_moves = 1;
9901                 programStats.moves_left = 1;
9902                 programStats.nodes = 1;
9903                 programStats.time = 1;
9904                 programStats.got_only_move = 1;
9905
9906                 /* Not really, but we also use this member to
9907                    mean "line isn't going to change" (Crafty
9908                    isn't searching, so stats won't change) */
9909                 programStats.line_is_book = 1;
9910
9911                 SendProgramStatsToFrontend( cps, &programStats );
9912
9913                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9914                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9915                     DisplayMove(currentMove - 1);
9916                 }
9917                 return;
9918             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9919                               &time, &nodes, &plylev, &mvleft,
9920                               &mvtot, mvname) >= 5) {
9921                 /* The stat01: line is from Crafty (9.29+) in response
9922                    to the "." command */
9923                 programStats.seen_stat = 1;
9924                 cps->maybeThinking = TRUE;
9925
9926                 if (programStats.got_only_move || !appData.periodicUpdates)
9927                   return;
9928
9929                 programStats.depth = plylev;
9930                 programStats.time = time;
9931                 programStats.nodes = nodes;
9932                 programStats.moves_left = mvleft;
9933                 programStats.nr_moves = mvtot;
9934                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9935                 programStats.ok_to_send = 1;
9936                 programStats.movelist[0] = '\0';
9937
9938                 SendProgramStatsToFrontend( cps, &programStats );
9939
9940                 return;
9941
9942             } else if (strncmp(message,"++",2) == 0) {
9943                 /* Crafty 9.29+ outputs this */
9944                 programStats.got_fail = 2;
9945                 return;
9946
9947             } else if (strncmp(message,"--",2) == 0) {
9948                 /* Crafty 9.29+ outputs this */
9949                 programStats.got_fail = 1;
9950                 return;
9951
9952             } else if (thinkOutput[0] != NULLCHAR &&
9953                        strncmp(message, "    ", 4) == 0) {
9954                 unsigned message_len;
9955
9956                 p = message;
9957                 while (*p && *p == ' ') p++;
9958
9959                 message_len = strlen( p );
9960
9961                 /* [AS] Avoid buffer overflow */
9962                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9963                     strcat(thinkOutput, " ");
9964                     strcat(thinkOutput, p);
9965                 }
9966
9967                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9968                     strcat(programStats.movelist, " ");
9969                     strcat(programStats.movelist, p);
9970                 }
9971
9972                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9973                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9974                     DisplayMove(currentMove - 1);
9975                 }
9976                 return;
9977             }
9978         }
9979         else {
9980             buf1[0] = NULLCHAR;
9981
9982             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9983                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9984             {
9985                 ChessProgramStats cpstats;
9986
9987                 if (plyext != ' ' && plyext != '\t') {
9988                     time *= 100;
9989                 }
9990
9991                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9992                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9993                     curscore = -curscore;
9994                 }
9995
9996                 cpstats.depth = plylev;
9997                 cpstats.nodes = nodes;
9998                 cpstats.time = time;
9999                 cpstats.score = curscore;
10000                 cpstats.got_only_move = 0;
10001                 cpstats.movelist[0] = '\0';
10002
10003                 if (buf1[0] != NULLCHAR) {
10004                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10005                 }
10006
10007                 cpstats.ok_to_send = 0;
10008                 cpstats.line_is_book = 0;
10009                 cpstats.nr_moves = 0;
10010                 cpstats.moves_left = 0;
10011
10012                 SendProgramStatsToFrontend( cps, &cpstats );
10013             }
10014         }
10015     }
10016 }
10017
10018
10019 /* Parse a game score from the character string "game", and
10020    record it as the history of the current game.  The game
10021    score is NOT assumed to start from the standard position.
10022    The display is not updated in any way.
10023    */
10024 void
10025 ParseGameHistory (char *game)
10026 {
10027     ChessMove moveType;
10028     int fromX, fromY, toX, toY, boardIndex;
10029     char promoChar;
10030     char *p, *q;
10031     char buf[MSG_SIZ];
10032
10033     if (appData.debugMode)
10034       fprintf(debugFP, "Parsing game history: %s\n", game);
10035
10036     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10037     gameInfo.site = StrSave(appData.icsHost);
10038     gameInfo.date = PGNDate();
10039     gameInfo.round = StrSave("-");
10040
10041     /* Parse out names of players */
10042     while (*game == ' ') game++;
10043     p = buf;
10044     while (*game != ' ') *p++ = *game++;
10045     *p = NULLCHAR;
10046     gameInfo.white = StrSave(buf);
10047     while (*game == ' ') game++;
10048     p = buf;
10049     while (*game != ' ' && *game != '\n') *p++ = *game++;
10050     *p = NULLCHAR;
10051     gameInfo.black = StrSave(buf);
10052
10053     /* Parse moves */
10054     boardIndex = blackPlaysFirst ? 1 : 0;
10055     yynewstr(game);
10056     for (;;) {
10057         yyboardindex = boardIndex;
10058         moveType = (ChessMove) Myylex();
10059         switch (moveType) {
10060           case IllegalMove:             /* maybe suicide chess, etc. */
10061   if (appData.debugMode) {
10062     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10063     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10064     setbuf(debugFP, NULL);
10065   }
10066           case WhitePromotion:
10067           case BlackPromotion:
10068           case WhiteNonPromotion:
10069           case BlackNonPromotion:
10070           case NormalMove:
10071           case FirstLeg:
10072           case WhiteCapturesEnPassant:
10073           case BlackCapturesEnPassant:
10074           case WhiteKingSideCastle:
10075           case WhiteQueenSideCastle:
10076           case BlackKingSideCastle:
10077           case BlackQueenSideCastle:
10078           case WhiteKingSideCastleWild:
10079           case WhiteQueenSideCastleWild:
10080           case BlackKingSideCastleWild:
10081           case BlackQueenSideCastleWild:
10082           /* PUSH Fabien */
10083           case WhiteHSideCastleFR:
10084           case WhiteASideCastleFR:
10085           case BlackHSideCastleFR:
10086           case BlackASideCastleFR:
10087           /* POP Fabien */
10088             fromX = currentMoveString[0] - AAA;
10089             fromY = currentMoveString[1] - ONE;
10090             toX = currentMoveString[2] - AAA;
10091             toY = currentMoveString[3] - ONE;
10092             promoChar = currentMoveString[4];
10093             break;
10094           case WhiteDrop:
10095           case BlackDrop:
10096             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10097             fromX = moveType == WhiteDrop ?
10098               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10099             (int) CharToPiece(ToLower(currentMoveString[0]));
10100             fromY = DROP_RANK;
10101             toX = currentMoveString[2] - AAA;
10102             toY = currentMoveString[3] - ONE;
10103             promoChar = NULLCHAR;
10104             break;
10105           case AmbiguousMove:
10106             /* bug? */
10107             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10108   if (appData.debugMode) {
10109     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10110     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10111     setbuf(debugFP, NULL);
10112   }
10113             DisplayError(buf, 0);
10114             return;
10115           case ImpossibleMove:
10116             /* bug? */
10117             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10118   if (appData.debugMode) {
10119     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10120     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10121     setbuf(debugFP, NULL);
10122   }
10123             DisplayError(buf, 0);
10124             return;
10125           case EndOfFile:
10126             if (boardIndex < backwardMostMove) {
10127                 /* Oops, gap.  How did that happen? */
10128                 DisplayError(_("Gap in move list"), 0);
10129                 return;
10130             }
10131             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10132             if (boardIndex > forwardMostMove) {
10133                 forwardMostMove = boardIndex;
10134             }
10135             return;
10136           case ElapsedTime:
10137             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10138                 strcat(parseList[boardIndex-1], " ");
10139                 strcat(parseList[boardIndex-1], yy_text);
10140             }
10141             continue;
10142           case Comment:
10143           case PGNTag:
10144           case NAG:
10145           default:
10146             /* ignore */
10147             continue;
10148           case WhiteWins:
10149           case BlackWins:
10150           case GameIsDrawn:
10151           case GameUnfinished:
10152             if (gameMode == IcsExamining) {
10153                 if (boardIndex < backwardMostMove) {
10154                     /* Oops, gap.  How did that happen? */
10155                     return;
10156                 }
10157                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10158                 return;
10159             }
10160             gameInfo.result = moveType;
10161             p = strchr(yy_text, '{');
10162             if (p == NULL) p = strchr(yy_text, '(');
10163             if (p == NULL) {
10164                 p = yy_text;
10165                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10166             } else {
10167                 q = strchr(p, *p == '{' ? '}' : ')');
10168                 if (q != NULL) *q = NULLCHAR;
10169                 p++;
10170             }
10171             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10172             gameInfo.resultDetails = StrSave(p);
10173             continue;
10174         }
10175         if (boardIndex >= forwardMostMove &&
10176             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10177             backwardMostMove = blackPlaysFirst ? 1 : 0;
10178             return;
10179         }
10180         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10181                                  fromY, fromX, toY, toX, promoChar,
10182                                  parseList[boardIndex]);
10183         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10184         /* currentMoveString is set as a side-effect of yylex */
10185         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10186         strcat(moveList[boardIndex], "\n");
10187         boardIndex++;
10188         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10189         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10190           case MT_NONE:
10191           case MT_STALEMATE:
10192           default:
10193             break;
10194           case MT_CHECK:
10195             if(!IS_SHOGI(gameInfo.variant))
10196                 strcat(parseList[boardIndex - 1], "+");
10197             break;
10198           case MT_CHECKMATE:
10199           case MT_STAINMATE:
10200             strcat(parseList[boardIndex - 1], "#");
10201             break;
10202         }
10203     }
10204 }
10205
10206
10207 /* Apply a move to the given board  */
10208 void
10209 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10210 {
10211   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10212   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10213
10214     /* [HGM] compute & store e.p. status and castling rights for new position */
10215     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10216
10217       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10218       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10219       board[EP_STATUS] = EP_NONE;
10220       board[EP_FILE] = board[EP_RANK] = 100;
10221
10222   if (fromY == DROP_RANK) {
10223         /* must be first */
10224         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10225             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10226             return;
10227         }
10228         piece = board[toY][toX] = (ChessSquare) fromX;
10229   } else {
10230 //      ChessSquare victim;
10231       int i;
10232
10233       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10234 //           victim = board[killY][killX],
10235            killed = board[killY][killX],
10236            board[killY][killX] = EmptySquare,
10237            board[EP_STATUS] = EP_CAPTURE;
10238            if( kill2X >= 0 && kill2Y >= 0)
10239              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10240       }
10241
10242       if( board[toY][toX] != EmptySquare ) {
10243            board[EP_STATUS] = EP_CAPTURE;
10244            if( (fromX != toX || fromY != toY) && // not igui!
10245                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10246                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10247                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10248            }
10249       }
10250
10251       pawn = board[fromY][fromX];
10252       if( pawn == WhiteLance || pawn == BlackLance ) {
10253            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10254                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10255                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10256            }
10257       }
10258       if( pawn == WhitePawn ) {
10259            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10260                board[EP_STATUS] = EP_PAWN_MOVE;
10261            if( toY-fromY>=2) {
10262                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10263                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10264                         gameInfo.variant != VariantBerolina || toX < fromX)
10265                       board[EP_STATUS] = toX | berolina;
10266                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10267                         gameInfo.variant != VariantBerolina || toX > fromX)
10268                       board[EP_STATUS] = toX;
10269            }
10270       } else
10271       if( pawn == BlackPawn ) {
10272            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10273                board[EP_STATUS] = EP_PAWN_MOVE;
10274            if( toY-fromY<= -2) {
10275                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10276                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10277                         gameInfo.variant != VariantBerolina || toX < fromX)
10278                       board[EP_STATUS] = toX | berolina;
10279                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10280                         gameInfo.variant != VariantBerolina || toX > fromX)
10281                       board[EP_STATUS] = toX;
10282            }
10283        }
10284
10285        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10286        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10287        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10288        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10289
10290        for(i=0; i<nrCastlingRights; i++) {
10291            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10292               board[CASTLING][i] == toX   && castlingRank[i] == toY
10293              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10294        }
10295
10296        if(gameInfo.variant == VariantSChess) { // update virginity
10297            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10298            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10299            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10300            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10301        }
10302
10303      if (fromX == toX && fromY == toY && killX < 0) return;
10304
10305      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10306      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10307      if(gameInfo.variant == VariantKnightmate)
10308          king += (int) WhiteUnicorn - (int) WhiteKing;
10309
10310     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10311        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10312         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10313         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10314         board[EP_STATUS] = EP_NONE; // capture was fake!
10315     } else
10316     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10317         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10318         board[toY][toX] = piece;
10319         board[EP_STATUS] = EP_NONE; // capture was fake!
10320     } else
10321     /* Code added by Tord: */
10322     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10323     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10324         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10325       board[EP_STATUS] = EP_NONE; // capture was fake!
10326       board[fromY][fromX] = EmptySquare;
10327       board[toY][toX] = EmptySquare;
10328       if((toX > fromX) != (piece == WhiteRook)) {
10329         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10330       } else {
10331         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10332       }
10333     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10334                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10335       board[EP_STATUS] = EP_NONE;
10336       board[fromY][fromX] = EmptySquare;
10337       board[toY][toX] = EmptySquare;
10338       if((toX > fromX) != (piece == BlackRook)) {
10339         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10340       } else {
10341         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10342       }
10343     /* End of code added by Tord */
10344
10345     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10346         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10347         board[toY][toX] = piece;
10348     } else if (board[fromY][fromX] == king
10349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10350         && toY == fromY && toX > fromX+1) {
10351         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10352                                                                                              ; // castle with nearest piece
10353         board[fromY][toX-1] = board[fromY][rookX];
10354         board[fromY][rookX] = EmptySquare;
10355         board[fromY][fromX] = EmptySquare;
10356         board[toY][toX] = king;
10357     } else if (board[fromY][fromX] == king
10358         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10359                && toY == fromY && toX < fromX-1) {
10360         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10361                                                                                   ; // castle with nearest piece
10362         board[fromY][toX+1] = board[fromY][rookX];
10363         board[fromY][rookX] = EmptySquare;
10364         board[fromY][fromX] = EmptySquare;
10365         board[toY][toX] = king;
10366     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10367                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10368                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10369                ) {
10370         /* white pawn promotion */
10371         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10372         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10373             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10374         board[fromY][fromX] = EmptySquare;
10375     } else if ((fromY >= BOARD_HEIGHT>>1)
10376                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10377                && (toX != fromX)
10378                && gameInfo.variant != VariantXiangqi
10379                && gameInfo.variant != VariantBerolina
10380                && (pawn == WhitePawn)
10381                && (board[toY][toX] == EmptySquare)) {
10382         board[fromY][fromX] = EmptySquare;
10383         board[toY][toX] = piece;
10384         if(toY == epRank - 128 + 1)
10385             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10386         else
10387             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10388     } else if ((fromY == BOARD_HEIGHT-4)
10389                && (toX == fromX)
10390                && gameInfo.variant == VariantBerolina
10391                && (board[fromY][fromX] == WhitePawn)
10392                && (board[toY][toX] == EmptySquare)) {
10393         board[fromY][fromX] = EmptySquare;
10394         board[toY][toX] = WhitePawn;
10395         if(oldEP & EP_BEROLIN_A) {
10396                 captured = board[fromY][fromX-1];
10397                 board[fromY][fromX-1] = EmptySquare;
10398         }else{  captured = board[fromY][fromX+1];
10399                 board[fromY][fromX+1] = EmptySquare;
10400         }
10401     } else if (board[fromY][fromX] == king
10402         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10403                && toY == fromY && toX > fromX+1) {
10404         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10405                                                                                              ;
10406         board[fromY][toX-1] = board[fromY][rookX];
10407         board[fromY][rookX] = EmptySquare;
10408         board[fromY][fromX] = EmptySquare;
10409         board[toY][toX] = king;
10410     } else if (board[fromY][fromX] == king
10411         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10412                && toY == fromY && toX < fromX-1) {
10413         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10414                                                                                 ;
10415         board[fromY][toX+1] = board[fromY][rookX];
10416         board[fromY][rookX] = EmptySquare;
10417         board[fromY][fromX] = EmptySquare;
10418         board[toY][toX] = king;
10419     } else if (fromY == 7 && fromX == 3
10420                && board[fromY][fromX] == BlackKing
10421                && toY == 7 && toX == 5) {
10422         board[fromY][fromX] = EmptySquare;
10423         board[toY][toX] = BlackKing;
10424         board[fromY][7] = EmptySquare;
10425         board[toY][4] = BlackRook;
10426     } else if (fromY == 7 && fromX == 3
10427                && board[fromY][fromX] == BlackKing
10428                && toY == 7 && toX == 1) {
10429         board[fromY][fromX] = EmptySquare;
10430         board[toY][toX] = BlackKing;
10431         board[fromY][0] = EmptySquare;
10432         board[toY][2] = BlackRook;
10433     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10434                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10435                && toY < promoRank && promoChar
10436                ) {
10437         /* black pawn promotion */
10438         board[toY][toX] = CharToPiece(ToLower(promoChar));
10439         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10440             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10441         board[fromY][fromX] = EmptySquare;
10442     } else if ((fromY < BOARD_HEIGHT>>1)
10443                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10444                && (toX != fromX)
10445                && gameInfo.variant != VariantXiangqi
10446                && gameInfo.variant != VariantBerolina
10447                && (pawn == BlackPawn)
10448                && (board[toY][toX] == EmptySquare)) {
10449         board[fromY][fromX] = EmptySquare;
10450         board[toY][toX] = piece;
10451         if(toY == epRank - 128 - 1)
10452             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10453         else
10454             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10455     } else if ((fromY == 3)
10456                && (toX == fromX)
10457                && gameInfo.variant == VariantBerolina
10458                && (board[fromY][fromX] == BlackPawn)
10459                && (board[toY][toX] == EmptySquare)) {
10460         board[fromY][fromX] = EmptySquare;
10461         board[toY][toX] = BlackPawn;
10462         if(oldEP & EP_BEROLIN_A) {
10463                 captured = board[fromY][fromX-1];
10464                 board[fromY][fromX-1] = EmptySquare;
10465         }else{  captured = board[fromY][fromX+1];
10466                 board[fromY][fromX+1] = EmptySquare;
10467         }
10468     } else {
10469         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10470         board[fromY][fromX] = EmptySquare;
10471         board[toY][toX] = piece;
10472     }
10473   }
10474
10475     if (gameInfo.holdingsWidth != 0) {
10476
10477       /* !!A lot more code needs to be written to support holdings  */
10478       /* [HGM] OK, so I have written it. Holdings are stored in the */
10479       /* penultimate board files, so they are automaticlly stored   */
10480       /* in the game history.                                       */
10481       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10482                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10483         /* Delete from holdings, by decreasing count */
10484         /* and erasing image if necessary            */
10485         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10486         if(p < (int) BlackPawn) { /* white drop */
10487              p -= (int)WhitePawn;
10488                  p = PieceToNumber((ChessSquare)p);
10489              if(p >= gameInfo.holdingsSize) p = 0;
10490              if(--board[p][BOARD_WIDTH-2] <= 0)
10491                   board[p][BOARD_WIDTH-1] = EmptySquare;
10492              if((int)board[p][BOARD_WIDTH-2] < 0)
10493                         board[p][BOARD_WIDTH-2] = 0;
10494         } else {                  /* black drop */
10495              p -= (int)BlackPawn;
10496                  p = PieceToNumber((ChessSquare)p);
10497              if(p >= gameInfo.holdingsSize) p = 0;
10498              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10499                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10500              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10501                         board[BOARD_HEIGHT-1-p][1] = 0;
10502         }
10503       }
10504       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10505           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10506         /* [HGM] holdings: Add to holdings, if holdings exist */
10507         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10508                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10509                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10510         }
10511         p = (int) captured;
10512         if (p >= (int) BlackPawn) {
10513           p -= (int)BlackPawn;
10514           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10515                   /* Restore shogi-promoted piece to its original  first */
10516                   captured = (ChessSquare) (DEMOTED(captured));
10517                   p = DEMOTED(p);
10518           }
10519           p = PieceToNumber((ChessSquare)p);
10520           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10521           board[p][BOARD_WIDTH-2]++;
10522           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10523         } else {
10524           p -= (int)WhitePawn;
10525           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10526                   captured = (ChessSquare) (DEMOTED(captured));
10527                   p = DEMOTED(p);
10528           }
10529           p = PieceToNumber((ChessSquare)p);
10530           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10531           board[BOARD_HEIGHT-1-p][1]++;
10532           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10533         }
10534       }
10535     } else if (gameInfo.variant == VariantAtomic) {
10536       if (captured != EmptySquare) {
10537         int y, x;
10538         for (y = toY-1; y <= toY+1; y++) {
10539           for (x = toX-1; x <= toX+1; x++) {
10540             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10541                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10542               board[y][x] = EmptySquare;
10543             }
10544           }
10545         }
10546         board[toY][toX] = EmptySquare;
10547       }
10548     }
10549
10550     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10551         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10552     } else
10553     if(promoChar == '+') {
10554         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10555         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10556         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10557           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10558     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10559         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10560         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10561            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10562         board[toY][toX] = newPiece;
10563     }
10564     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10565                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10566         // [HGM] superchess: take promotion piece out of holdings
10567         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10568         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10569             if(!--board[k][BOARD_WIDTH-2])
10570                 board[k][BOARD_WIDTH-1] = EmptySquare;
10571         } else {
10572             if(!--board[BOARD_HEIGHT-1-k][1])
10573                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10574         }
10575     }
10576 }
10577
10578 /* Updates forwardMostMove */
10579 void
10580 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10581 {
10582     int x = toX, y = toY;
10583     char *s = parseList[forwardMostMove];
10584     ChessSquare p = boards[forwardMostMove][toY][toX];
10585 //    forwardMostMove++; // [HGM] bare: moved downstream
10586
10587     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10588     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10589     (void) CoordsToAlgebraic(boards[forwardMostMove],
10590                              PosFlags(forwardMostMove),
10591                              fromY, fromX, y, x, (killX < 0)*promoChar,
10592                              s);
10593     if(kill2X >= 0 && kill2Y >= 0)
10594         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10595     if(killX >= 0 && killY >= 0)
10596         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10597                                            toX + AAA, toY + ONE - '0', promoChar);
10598
10599     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10600         int timeLeft; static int lastLoadFlag=0; int king, piece;
10601         piece = boards[forwardMostMove][fromY][fromX];
10602         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10603         if(gameInfo.variant == VariantKnightmate)
10604             king += (int) WhiteUnicorn - (int) WhiteKing;
10605         if(forwardMostMove == 0) {
10606             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10607                 fprintf(serverMoves, "%s;", UserName());
10608             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10609                 fprintf(serverMoves, "%s;", second.tidy);
10610             fprintf(serverMoves, "%s;", first.tidy);
10611             if(gameMode == MachinePlaysWhite)
10612                 fprintf(serverMoves, "%s;", UserName());
10613             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10614                 fprintf(serverMoves, "%s;", second.tidy);
10615         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10616         lastLoadFlag = loadFlag;
10617         // print base move
10618         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10619         // print castling suffix
10620         if( toY == fromY && piece == king ) {
10621             if(toX-fromX > 1)
10622                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10623             if(fromX-toX >1)
10624                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10625         }
10626         // e.p. suffix
10627         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10628              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10629              boards[forwardMostMove][toY][toX] == EmptySquare
10630              && fromX != toX && fromY != toY)
10631                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10632         // promotion suffix
10633         if(promoChar != NULLCHAR) {
10634             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10635                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10636                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10637             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10638         }
10639         if(!loadFlag) {
10640                 char buf[MOVE_LEN*2], *p; int len;
10641             fprintf(serverMoves, "/%d/%d",
10642                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10643             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10644             else                      timeLeft = blackTimeRemaining/1000;
10645             fprintf(serverMoves, "/%d", timeLeft);
10646                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10647                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10648                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10649                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10650             fprintf(serverMoves, "/%s", buf);
10651         }
10652         fflush(serverMoves);
10653     }
10654
10655     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10656         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10657       return;
10658     }
10659     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10660     if (commentList[forwardMostMove+1] != NULL) {
10661         free(commentList[forwardMostMove+1]);
10662         commentList[forwardMostMove+1] = NULL;
10663     }
10664     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10665     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10666     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10667     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10668     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10669     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10670     adjustedClock = FALSE;
10671     gameInfo.result = GameUnfinished;
10672     if (gameInfo.resultDetails != NULL) {
10673         free(gameInfo.resultDetails);
10674         gameInfo.resultDetails = NULL;
10675     }
10676     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10677                               moveList[forwardMostMove - 1]);
10678     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10679       case MT_NONE:
10680       case MT_STALEMATE:
10681       default:
10682         break;
10683       case MT_CHECK:
10684         if(!IS_SHOGI(gameInfo.variant))
10685             strcat(parseList[forwardMostMove - 1], "+");
10686         break;
10687       case MT_CHECKMATE:
10688       case MT_STAINMATE:
10689         strcat(parseList[forwardMostMove - 1], "#");
10690         break;
10691     }
10692 }
10693
10694 /* Updates currentMove if not pausing */
10695 void
10696 ShowMove (int fromX, int fromY, int toX, int toY)
10697 {
10698     int instant = (gameMode == PlayFromGameFile) ?
10699         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10700     if(appData.noGUI) return;
10701     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10702         if (!instant) {
10703             if (forwardMostMove == currentMove + 1) {
10704                 AnimateMove(boards[forwardMostMove - 1],
10705                             fromX, fromY, toX, toY);
10706             }
10707         }
10708         currentMove = forwardMostMove;
10709     }
10710
10711     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10712
10713     if (instant) return;
10714
10715     DisplayMove(currentMove - 1);
10716     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10717             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10718                 SetHighlights(fromX, fromY, toX, toY);
10719             }
10720     }
10721     DrawPosition(FALSE, boards[currentMove]);
10722     DisplayBothClocks();
10723     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10724 }
10725
10726 void
10727 SendEgtPath (ChessProgramState *cps)
10728 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10729         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10730
10731         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10732
10733         while(*p) {
10734             char c, *q = name+1, *r, *s;
10735
10736             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10737             while(*p && *p != ',') *q++ = *p++;
10738             *q++ = ':'; *q = 0;
10739             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10740                 strcmp(name, ",nalimov:") == 0 ) {
10741                 // take nalimov path from the menu-changeable option first, if it is defined
10742               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10743                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10744             } else
10745             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10746                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10747                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10748                 s = r = StrStr(s, ":") + 1; // beginning of path info
10749                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10750                 c = *r; *r = 0;             // temporarily null-terminate path info
10751                     *--q = 0;               // strip of trailig ':' from name
10752                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10753                 *r = c;
10754                 SendToProgram(buf,cps);     // send egtbpath command for this format
10755             }
10756             if(*p == ',') p++; // read away comma to position for next format name
10757         }
10758 }
10759
10760 static int
10761 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10762 {
10763       int width = 8, height = 8, holdings = 0;             // most common sizes
10764       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10765       // correct the deviations default for each variant
10766       if( v == VariantXiangqi ) width = 9,  height = 10;
10767       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10768       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10769       if( v == VariantCapablanca || v == VariantCapaRandom ||
10770           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10771                                 width = 10;
10772       if( v == VariantCourier ) width = 12;
10773       if( v == VariantSuper )                            holdings = 8;
10774       if( v == VariantGreat )   width = 10,              holdings = 8;
10775       if( v == VariantSChess )                           holdings = 7;
10776       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10777       if( v == VariantChuChess) width = 10, height = 10;
10778       if( v == VariantChu )     width = 12, height = 12;
10779       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10780              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10781              holdingsSize >= 0 && holdingsSize != holdings;
10782 }
10783
10784 char variantError[MSG_SIZ];
10785
10786 char *
10787 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10788 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10789       char *p, *variant = VariantName(v);
10790       static char b[MSG_SIZ];
10791       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10792            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10793                                                holdingsSize, variant); // cook up sized variant name
10794            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10795            if(StrStr(list, b) == NULL) {
10796                // specific sized variant not known, check if general sizing allowed
10797                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10798                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10799                             boardWidth, boardHeight, holdingsSize, engine);
10800                    return NULL;
10801                }
10802                /* [HGM] here we really should compare with the maximum supported board size */
10803            }
10804       } else snprintf(b, MSG_SIZ,"%s", variant);
10805       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10806       p = StrStr(list, b);
10807       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10808       if(p == NULL) {
10809           // occurs not at all in list, or only as sub-string
10810           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10811           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10812               int l = strlen(variantError);
10813               char *q;
10814               while(p != list && p[-1] != ',') p--;
10815               q = strchr(p, ',');
10816               if(q) *q = NULLCHAR;
10817               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10818               if(q) *q= ',';
10819           }
10820           return NULL;
10821       }
10822       return b;
10823 }
10824
10825 void
10826 InitChessProgram (ChessProgramState *cps, int setup)
10827 /* setup needed to setup FRC opening position */
10828 {
10829     char buf[MSG_SIZ], *b;
10830     if (appData.noChessProgram) return;
10831     hintRequested = FALSE;
10832     bookRequested = FALSE;
10833
10834     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10835     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10836     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10837     if(cps->memSize) { /* [HGM] memory */
10838       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10839         SendToProgram(buf, cps);
10840     }
10841     SendEgtPath(cps); /* [HGM] EGT */
10842     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10843       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10844         SendToProgram(buf, cps);
10845     }
10846
10847     setboardSpoiledMachineBlack = FALSE;
10848     SendToProgram(cps->initString, cps);
10849     if (gameInfo.variant != VariantNormal &&
10850         gameInfo.variant != VariantLoadable
10851         /* [HGM] also send variant if board size non-standard */
10852         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10853
10854       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10855                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10856
10857       if (b == NULL) {
10858         VariantClass v;
10859         char c, *q = cps->variants, *p = strchr(q, ',');
10860         if(p) *p = NULLCHAR;
10861         v = StringToVariant(q);
10862         DisplayError(variantError, 0);
10863         if(v != VariantUnknown && cps == &first) {
10864             int w, h, s;
10865             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10866                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10867             ASSIGN(appData.variant, q);
10868             Reset(TRUE, FALSE);
10869         }
10870         if(p) *p = ',';
10871         return;
10872       }
10873
10874       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10875       SendToProgram(buf, cps);
10876     }
10877     currentlyInitializedVariant = gameInfo.variant;
10878
10879     /* [HGM] send opening position in FRC to first engine */
10880     if(setup) {
10881           SendToProgram("force\n", cps);
10882           SendBoard(cps, 0);
10883           /* engine is now in force mode! Set flag to wake it up after first move. */
10884           setboardSpoiledMachineBlack = 1;
10885     }
10886
10887     if (cps->sendICS) {
10888       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10889       SendToProgram(buf, cps);
10890     }
10891     cps->maybeThinking = FALSE;
10892     cps->offeredDraw = 0;
10893     if (!appData.icsActive) {
10894         SendTimeControl(cps, movesPerSession, timeControl,
10895                         timeIncrement, appData.searchDepth,
10896                         searchTime);
10897     }
10898     if (appData.showThinking
10899         // [HGM] thinking: four options require thinking output to be sent
10900         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10901                                 ) {
10902         SendToProgram("post\n", cps);
10903     }
10904     SendToProgram("hard\n", cps);
10905     if (!appData.ponderNextMove) {
10906         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10907            it without being sure what state we are in first.  "hard"
10908            is not a toggle, so that one is OK.
10909          */
10910         SendToProgram("easy\n", cps);
10911     }
10912     if (cps->usePing) {
10913       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10914       SendToProgram(buf, cps);
10915     }
10916     cps->initDone = TRUE;
10917     ClearEngineOutputPane(cps == &second);
10918 }
10919
10920
10921 void
10922 ResendOptions (ChessProgramState *cps)
10923 { // send the stored value of the options
10924   int i;
10925   char buf[MSG_SIZ];
10926   Option *opt = cps->option;
10927   for(i=0; i<cps->nrOptions; i++, opt++) {
10928       switch(opt->type) {
10929         case Spin:
10930         case Slider:
10931         case CheckBox:
10932             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10933           break;
10934         case ComboBox:
10935           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10936           break;
10937         default:
10938             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10939           break;
10940         case Button:
10941         case SaveButton:
10942           continue;
10943       }
10944       SendToProgram(buf, cps);
10945   }
10946 }
10947
10948 void
10949 StartChessProgram (ChessProgramState *cps)
10950 {
10951     char buf[MSG_SIZ];
10952     int err;
10953
10954     if (appData.noChessProgram) return;
10955     cps->initDone = FALSE;
10956
10957     if (strcmp(cps->host, "localhost") == 0) {
10958         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10959     } else if (*appData.remoteShell == NULLCHAR) {
10960         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10961     } else {
10962         if (*appData.remoteUser == NULLCHAR) {
10963           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10964                     cps->program);
10965         } else {
10966           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10967                     cps->host, appData.remoteUser, cps->program);
10968         }
10969         err = StartChildProcess(buf, "", &cps->pr);
10970     }
10971
10972     if (err != 0) {
10973       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10974         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10975         if(cps != &first) return;
10976         appData.noChessProgram = TRUE;
10977         ThawUI();
10978         SetNCPMode();
10979 //      DisplayFatalError(buf, err, 1);
10980 //      cps->pr = NoProc;
10981 //      cps->isr = NULL;
10982         return;
10983     }
10984
10985     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10986     if (cps->protocolVersion > 1) {
10987       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10988       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10989         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10990         cps->comboCnt = 0;  //                and values of combo boxes
10991       }
10992       SendToProgram(buf, cps);
10993       if(cps->reload) ResendOptions(cps);
10994     } else {
10995       SendToProgram("xboard\n", cps);
10996     }
10997 }
10998
10999 void
11000 TwoMachinesEventIfReady P((void))
11001 {
11002   static int curMess = 0;
11003   if (first.lastPing != first.lastPong) {
11004     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11005     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11006     return;
11007   }
11008   if (second.lastPing != second.lastPong) {
11009     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11010     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11011     return;
11012   }
11013   DisplayMessage("", ""); curMess = 0;
11014   TwoMachinesEvent();
11015 }
11016
11017 char *
11018 MakeName (char *template)
11019 {
11020     time_t clock;
11021     struct tm *tm;
11022     static char buf[MSG_SIZ];
11023     char *p = buf;
11024     int i;
11025
11026     clock = time((time_t *)NULL);
11027     tm = localtime(&clock);
11028
11029     while(*p++ = *template++) if(p[-1] == '%') {
11030         switch(*template++) {
11031           case 0:   *p = 0; return buf;
11032           case 'Y': i = tm->tm_year+1900; break;
11033           case 'y': i = tm->tm_year-100; break;
11034           case 'M': i = tm->tm_mon+1; break;
11035           case 'd': i = tm->tm_mday; break;
11036           case 'h': i = tm->tm_hour; break;
11037           case 'm': i = tm->tm_min; break;
11038           case 's': i = tm->tm_sec; break;
11039           default:  i = 0;
11040         }
11041         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11042     }
11043     return buf;
11044 }
11045
11046 int
11047 CountPlayers (char *p)
11048 {
11049     int n = 0;
11050     while(p = strchr(p, '\n')) p++, n++; // count participants
11051     return n;
11052 }
11053
11054 FILE *
11055 WriteTourneyFile (char *results, FILE *f)
11056 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11057     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11058     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11059         // create a file with tournament description
11060         fprintf(f, "-participants {%s}\n", appData.participants);
11061         fprintf(f, "-seedBase %d\n", appData.seedBase);
11062         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11063         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11064         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11065         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11066         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11067         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11068         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11069         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11070         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11071         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11072         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11073         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11074         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11075         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11076         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11077         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11078         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11079         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11080         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11081         fprintf(f, "-smpCores %d\n", appData.smpCores);
11082         if(searchTime > 0)
11083                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11084         else {
11085                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11086                 fprintf(f, "-tc %s\n", appData.timeControl);
11087                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11088         }
11089         fprintf(f, "-results \"%s\"\n", results);
11090     }
11091     return f;
11092 }
11093
11094 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11095
11096 void
11097 Substitute (char *participants, int expunge)
11098 {
11099     int i, changed, changes=0, nPlayers=0;
11100     char *p, *q, *r, buf[MSG_SIZ];
11101     if(participants == NULL) return;
11102     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11103     r = p = participants; q = appData.participants;
11104     while(*p && *p == *q) {
11105         if(*p == '\n') r = p+1, nPlayers++;
11106         p++; q++;
11107     }
11108     if(*p) { // difference
11109         while(*p && *p++ != '\n')
11110                                  ;
11111         while(*q && *q++ != '\n')
11112                                  ;
11113       changed = nPlayers;
11114         changes = 1 + (strcmp(p, q) != 0);
11115     }
11116     if(changes == 1) { // a single engine mnemonic was changed
11117         q = r; while(*q) nPlayers += (*q++ == '\n');
11118         p = buf; while(*r && (*p = *r++) != '\n') p++;
11119         *p = NULLCHAR;
11120         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11121         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11122         if(mnemonic[i]) { // The substitute is valid
11123             FILE *f;
11124             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11125                 flock(fileno(f), LOCK_EX);
11126                 ParseArgsFromFile(f);
11127                 fseek(f, 0, SEEK_SET);
11128                 FREE(appData.participants); appData.participants = participants;
11129                 if(expunge) { // erase results of replaced engine
11130                     int len = strlen(appData.results), w, b, dummy;
11131                     for(i=0; i<len; i++) {
11132                         Pairing(i, nPlayers, &w, &b, &dummy);
11133                         if((w == changed || b == changed) && appData.results[i] == '*') {
11134                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11135                             fclose(f);
11136                             return;
11137                         }
11138                     }
11139                     for(i=0; i<len; i++) {
11140                         Pairing(i, nPlayers, &w, &b, &dummy);
11141                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11142                     }
11143                 }
11144                 WriteTourneyFile(appData.results, f);
11145                 fclose(f); // release lock
11146                 return;
11147             }
11148         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11149     }
11150     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11151     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11152     free(participants);
11153     return;
11154 }
11155
11156 int
11157 CheckPlayers (char *participants)
11158 {
11159         int i;
11160         char buf[MSG_SIZ], *p;
11161         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11162         while(p = strchr(participants, '\n')) {
11163             *p = NULLCHAR;
11164             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11165             if(!mnemonic[i]) {
11166                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11167                 *p = '\n';
11168                 DisplayError(buf, 0);
11169                 return 1;
11170             }
11171             *p = '\n';
11172             participants = p + 1;
11173         }
11174         return 0;
11175 }
11176
11177 int
11178 CreateTourney (char *name)
11179 {
11180         FILE *f;
11181         if(matchMode && strcmp(name, appData.tourneyFile)) {
11182              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11183         }
11184         if(name[0] == NULLCHAR) {
11185             if(appData.participants[0])
11186                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11187             return 0;
11188         }
11189         f = fopen(name, "r");
11190         if(f) { // file exists
11191             ASSIGN(appData.tourneyFile, name);
11192             ParseArgsFromFile(f); // parse it
11193         } else {
11194             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11195             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11196                 DisplayError(_("Not enough participants"), 0);
11197                 return 0;
11198             }
11199             if(CheckPlayers(appData.participants)) return 0;
11200             ASSIGN(appData.tourneyFile, name);
11201             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11202             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11203         }
11204         fclose(f);
11205         appData.noChessProgram = FALSE;
11206         appData.clockMode = TRUE;
11207         SetGNUMode();
11208         return 1;
11209 }
11210
11211 int
11212 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11213 {
11214     char buf[MSG_SIZ], *p, *q;
11215     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11216     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11217     skip = !all && group[0]; // if group requested, we start in skip mode
11218     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11219         p = names; q = buf; header = 0;
11220         while(*p && *p != '\n') *q++ = *p++;
11221         *q = 0;
11222         if(*p == '\n') p++;
11223         if(buf[0] == '#') {
11224             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11225             depth++; // we must be entering a new group
11226             if(all) continue; // suppress printing group headers when complete list requested
11227             header = 1;
11228             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11229         }
11230         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11231         if(engineList[i]) free(engineList[i]);
11232         engineList[i] = strdup(buf);
11233         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11234         if(engineMnemonic[i]) free(engineMnemonic[i]);
11235         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11236             strcat(buf, " (");
11237             sscanf(q + 8, "%s", buf + strlen(buf));
11238             strcat(buf, ")");
11239         }
11240         engineMnemonic[i] = strdup(buf);
11241         i++;
11242     }
11243     engineList[i] = engineMnemonic[i] = NULL;
11244     return i;
11245 }
11246
11247 // following implemented as macro to avoid type limitations
11248 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11249
11250 void
11251 SwapEngines (int n)
11252 {   // swap settings for first engine and other engine (so far only some selected options)
11253     int h;
11254     char *p;
11255     if(n == 0) return;
11256     SWAP(directory, p)
11257     SWAP(chessProgram, p)
11258     SWAP(isUCI, h)
11259     SWAP(hasOwnBookUCI, h)
11260     SWAP(protocolVersion, h)
11261     SWAP(reuse, h)
11262     SWAP(scoreIsAbsolute, h)
11263     SWAP(timeOdds, h)
11264     SWAP(logo, p)
11265     SWAP(pgnName, p)
11266     SWAP(pvSAN, h)
11267     SWAP(engOptions, p)
11268     SWAP(engInitString, p)
11269     SWAP(computerString, p)
11270     SWAP(features, p)
11271     SWAP(fenOverride, p)
11272     SWAP(NPS, h)
11273     SWAP(accumulateTC, h)
11274     SWAP(drawDepth, h)
11275     SWAP(host, p)
11276     SWAP(pseudo, h)
11277 }
11278
11279 int
11280 GetEngineLine (char *s, int n)
11281 {
11282     int i;
11283     char buf[MSG_SIZ];
11284     extern char *icsNames;
11285     if(!s || !*s) return 0;
11286     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11287     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11288     if(!mnemonic[i]) return 0;
11289     if(n == 11) return 1; // just testing if there was a match
11290     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11291     if(n == 1) SwapEngines(n);
11292     ParseArgsFromString(buf);
11293     if(n == 1) SwapEngines(n);
11294     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11295         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11296         ParseArgsFromString(buf);
11297     }
11298     return 1;
11299 }
11300
11301 int
11302 SetPlayer (int player, char *p)
11303 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11304     int i;
11305     char buf[MSG_SIZ], *engineName;
11306     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11307     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11308     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11309     if(mnemonic[i]) {
11310         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11311         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11312         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11313         ParseArgsFromString(buf);
11314     } else { // no engine with this nickname is installed!
11315         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11316         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11317         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11318         ModeHighlight();
11319         DisplayError(buf, 0);
11320         return 0;
11321     }
11322     free(engineName);
11323     return i;
11324 }
11325
11326 char *recentEngines;
11327
11328 void
11329 RecentEngineEvent (int nr)
11330 {
11331     int n;
11332 //    SwapEngines(1); // bump first to second
11333 //    ReplaceEngine(&second, 1); // and load it there
11334     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11335     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11336     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11337         ReplaceEngine(&first, 0);
11338         FloatToFront(&appData.recentEngineList, command[n]);
11339     }
11340 }
11341
11342 int
11343 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11344 {   // determine players from game number
11345     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11346
11347     if(appData.tourneyType == 0) {
11348         roundsPerCycle = (nPlayers - 1) | 1;
11349         pairingsPerRound = nPlayers / 2;
11350     } else if(appData.tourneyType > 0) {
11351         roundsPerCycle = nPlayers - appData.tourneyType;
11352         pairingsPerRound = appData.tourneyType;
11353     }
11354     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11355     gamesPerCycle = gamesPerRound * roundsPerCycle;
11356     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11357     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11358     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11359     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11360     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11361     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11362
11363     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11364     if(appData.roundSync) *syncInterval = gamesPerRound;
11365
11366     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11367
11368     if(appData.tourneyType == 0) {
11369         if(curPairing == (nPlayers-1)/2 ) {
11370             *whitePlayer = curRound;
11371             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11372         } else {
11373             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11374             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11375             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11376             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11377         }
11378     } else if(appData.tourneyType > 1) {
11379         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11380         *whitePlayer = curRound + appData.tourneyType;
11381     } else if(appData.tourneyType > 0) {
11382         *whitePlayer = curPairing;
11383         *blackPlayer = curRound + appData.tourneyType;
11384     }
11385
11386     // take care of white/black alternation per round.
11387     // For cycles and games this is already taken care of by default, derived from matchGame!
11388     return curRound & 1;
11389 }
11390
11391 int
11392 NextTourneyGame (int nr, int *swapColors)
11393 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11394     char *p, *q;
11395     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11396     FILE *tf;
11397     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11398     tf = fopen(appData.tourneyFile, "r");
11399     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11400     ParseArgsFromFile(tf); fclose(tf);
11401     InitTimeControls(); // TC might be altered from tourney file
11402
11403     nPlayers = CountPlayers(appData.participants); // count participants
11404     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11405     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11406
11407     if(syncInterval) {
11408         p = q = appData.results;
11409         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11410         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11411             DisplayMessage(_("Waiting for other game(s)"),"");
11412             waitingForGame = TRUE;
11413             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11414             return 0;
11415         }
11416         waitingForGame = FALSE;
11417     }
11418
11419     if(appData.tourneyType < 0) {
11420         if(nr>=0 && !pairingReceived) {
11421             char buf[1<<16];
11422             if(pairing.pr == NoProc) {
11423                 if(!appData.pairingEngine[0]) {
11424                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11425                     return 0;
11426                 }
11427                 StartChessProgram(&pairing); // starts the pairing engine
11428             }
11429             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11430             SendToProgram(buf, &pairing);
11431             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11432             SendToProgram(buf, &pairing);
11433             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11434         }
11435         pairingReceived = 0;                              // ... so we continue here
11436         *swapColors = 0;
11437         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11438         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11439         matchGame = 1; roundNr = nr / syncInterval + 1;
11440     }
11441
11442     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11443
11444     // redefine engines, engine dir, etc.
11445     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11446     if(first.pr == NoProc) {
11447       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11448       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11449     }
11450     if(second.pr == NoProc) {
11451       SwapEngines(1);
11452       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11453       SwapEngines(1);         // and make that valid for second engine by swapping
11454       InitEngine(&second, 1);
11455     }
11456     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11457     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11458     return OK;
11459 }
11460
11461 void
11462 NextMatchGame ()
11463 {   // performs game initialization that does not invoke engines, and then tries to start the game
11464     int res, firstWhite, swapColors = 0;
11465     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11466     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
11467         char buf[MSG_SIZ];
11468         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11469         if(strcmp(buf, currentDebugFile)) { // name has changed
11470             FILE *f = fopen(buf, "w");
11471             if(f) { // if opening the new file failed, just keep using the old one
11472                 ASSIGN(currentDebugFile, buf);
11473                 fclose(debugFP);
11474                 debugFP = f;
11475             }
11476             if(appData.serverFileName) {
11477                 if(serverFP) fclose(serverFP);
11478                 serverFP = fopen(appData.serverFileName, "w");
11479                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11480                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11481             }
11482         }
11483     }
11484     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11485     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11486     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11487     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11488     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11489     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11490     Reset(FALSE, first.pr != NoProc);
11491     res = LoadGameOrPosition(matchGame); // setup game
11492     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11493     if(!res) return; // abort when bad game/pos file
11494     if(appData.epd) {// in EPD mode we make sure first engine is to move
11495         firstWhite = !(forwardMostMove & 1);
11496         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11497         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11498     }
11499     TwoMachinesEvent();
11500 }
11501
11502 void
11503 UserAdjudicationEvent (int result)
11504 {
11505     ChessMove gameResult = GameIsDrawn;
11506
11507     if( result > 0 ) {
11508         gameResult = WhiteWins;
11509     }
11510     else if( result < 0 ) {
11511         gameResult = BlackWins;
11512     }
11513
11514     if( gameMode == TwoMachinesPlay ) {
11515         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11516     }
11517 }
11518
11519
11520 // [HGM] save: calculate checksum of game to make games easily identifiable
11521 int
11522 StringCheckSum (char *s)
11523 {
11524         int i = 0;
11525         if(s==NULL) return 0;
11526         while(*s) i = i*259 + *s++;
11527         return i;
11528 }
11529
11530 int
11531 GameCheckSum ()
11532 {
11533         int i, sum=0;
11534         for(i=backwardMostMove; i<forwardMostMove; i++) {
11535                 sum += pvInfoList[i].depth;
11536                 sum += StringCheckSum(parseList[i]);
11537                 sum += StringCheckSum(commentList[i]);
11538                 sum *= 261;
11539         }
11540         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11541         return sum + StringCheckSum(commentList[i]);
11542 } // end of save patch
11543
11544 void
11545 GameEnds (ChessMove result, char *resultDetails, int whosays)
11546 {
11547     GameMode nextGameMode;
11548     int isIcsGame;
11549     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11550
11551     if(endingGame) return; /* [HGM] crash: forbid recursion */
11552     endingGame = 1;
11553     if(twoBoards) { // [HGM] dual: switch back to one board
11554         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11555         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11556     }
11557     if (appData.debugMode) {
11558       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11559               result, resultDetails ? resultDetails : "(null)", whosays);
11560     }
11561
11562     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11563
11564     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11565
11566     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11567         /* If we are playing on ICS, the server decides when the
11568            game is over, but the engine can offer to draw, claim
11569            a draw, or resign.
11570          */
11571 #if ZIPPY
11572         if (appData.zippyPlay && first.initDone) {
11573             if (result == GameIsDrawn) {
11574                 /* In case draw still needs to be claimed */
11575                 SendToICS(ics_prefix);
11576                 SendToICS("draw\n");
11577             } else if (StrCaseStr(resultDetails, "resign")) {
11578                 SendToICS(ics_prefix);
11579                 SendToICS("resign\n");
11580             }
11581         }
11582 #endif
11583         endingGame = 0; /* [HGM] crash */
11584         return;
11585     }
11586
11587     /* If we're loading the game from a file, stop */
11588     if (whosays == GE_FILE) {
11589       (void) StopLoadGameTimer();
11590       gameFileFP = NULL;
11591     }
11592
11593     /* Cancel draw offers */
11594     first.offeredDraw = second.offeredDraw = 0;
11595
11596     /* If this is an ICS game, only ICS can really say it's done;
11597        if not, anyone can. */
11598     isIcsGame = (gameMode == IcsPlayingWhite ||
11599                  gameMode == IcsPlayingBlack ||
11600                  gameMode == IcsObserving    ||
11601                  gameMode == IcsExamining);
11602
11603     if (!isIcsGame || whosays == GE_ICS) {
11604         /* OK -- not an ICS game, or ICS said it was done */
11605         StopClocks();
11606         if (!isIcsGame && !appData.noChessProgram)
11607           SetUserThinkingEnables();
11608
11609         /* [HGM] if a machine claims the game end we verify this claim */
11610         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11611             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11612                 char claimer;
11613                 ChessMove trueResult = (ChessMove) -1;
11614
11615                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11616                                             first.twoMachinesColor[0] :
11617                                             second.twoMachinesColor[0] ;
11618
11619                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11620                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11621                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11622                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11623                 } else
11624                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11625                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11626                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11627                 } else
11628                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11629                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11630                 }
11631
11632                 // now verify win claims, but not in drop games, as we don't understand those yet
11633                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11634                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11635                     (result == WhiteWins && claimer == 'w' ||
11636                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11637                       if (appData.debugMode) {
11638                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11639                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11640                       }
11641                       if(result != trueResult) {
11642                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11643                               result = claimer == 'w' ? BlackWins : WhiteWins;
11644                               resultDetails = buf;
11645                       }
11646                 } else
11647                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11648                     && (forwardMostMove <= backwardMostMove ||
11649                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11650                         (claimer=='b')==(forwardMostMove&1))
11651                                                                                   ) {
11652                       /* [HGM] verify: draws that were not flagged are false claims */
11653                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11654                       result = claimer == 'w' ? BlackWins : WhiteWins;
11655                       resultDetails = buf;
11656                 }
11657                 /* (Claiming a loss is accepted no questions asked!) */
11658             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11659                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11660                 result = GameUnfinished;
11661                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11662             }
11663             /* [HGM] bare: don't allow bare King to win */
11664             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11665                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11666                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11667                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11668                && result != GameIsDrawn)
11669             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11670                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11671                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11672                         if(p >= 0 && p <= (int)WhiteKing) k++;
11673                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11674                 }
11675                 if (appData.debugMode) {
11676                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11677                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11678                 }
11679                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11680                         result = GameIsDrawn;
11681                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11682                         resultDetails = buf;
11683                 }
11684             }
11685         }
11686
11687
11688         if(serverMoves != NULL && !loadFlag) { char c = '=';
11689             if(result==WhiteWins) c = '+';
11690             if(result==BlackWins) c = '-';
11691             if(resultDetails != NULL)
11692                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11693         }
11694         if (resultDetails != NULL) {
11695             gameInfo.result = result;
11696             gameInfo.resultDetails = StrSave(resultDetails);
11697
11698             /* display last move only if game was not loaded from file */
11699             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11700                 DisplayMove(currentMove - 1);
11701
11702             if (forwardMostMove != 0) {
11703                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11704                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11705                                                                 ) {
11706                     if (*appData.saveGameFile != NULLCHAR) {
11707                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11708                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11709                         else
11710                         SaveGameToFile(appData.saveGameFile, TRUE);
11711                     } else if (appData.autoSaveGames) {
11712                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11713                     }
11714                     if (*appData.savePositionFile != NULLCHAR) {
11715                         SavePositionToFile(appData.savePositionFile);
11716                     }
11717                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11718                 }
11719             }
11720
11721             /* Tell program how game ended in case it is learning */
11722             /* [HGM] Moved this to after saving the PGN, just in case */
11723             /* engine died and we got here through time loss. In that */
11724             /* case we will get a fatal error writing the pipe, which */
11725             /* would otherwise lose us the PGN.                       */
11726             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11727             /* output during GameEnds should never be fatal anymore   */
11728             if (gameMode == MachinePlaysWhite ||
11729                 gameMode == MachinePlaysBlack ||
11730                 gameMode == TwoMachinesPlay ||
11731                 gameMode == IcsPlayingWhite ||
11732                 gameMode == IcsPlayingBlack ||
11733                 gameMode == BeginningOfGame) {
11734                 char buf[MSG_SIZ];
11735                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11736                         resultDetails);
11737                 if (first.pr != NoProc) {
11738                     SendToProgram(buf, &first);
11739                 }
11740                 if (second.pr != NoProc &&
11741                     gameMode == TwoMachinesPlay) {
11742                     SendToProgram(buf, &second);
11743                 }
11744             }
11745         }
11746
11747         if (appData.icsActive) {
11748             if (appData.quietPlay &&
11749                 (gameMode == IcsPlayingWhite ||
11750                  gameMode == IcsPlayingBlack)) {
11751                 SendToICS(ics_prefix);
11752                 SendToICS("set shout 1\n");
11753             }
11754             nextGameMode = IcsIdle;
11755             ics_user_moved = FALSE;
11756             /* clean up premove.  It's ugly when the game has ended and the
11757              * premove highlights are still on the board.
11758              */
11759             if (gotPremove) {
11760               gotPremove = FALSE;
11761               ClearPremoveHighlights();
11762               DrawPosition(FALSE, boards[currentMove]);
11763             }
11764             if (whosays == GE_ICS) {
11765                 switch (result) {
11766                 case WhiteWins:
11767                     if (gameMode == IcsPlayingWhite)
11768                         PlayIcsWinSound();
11769                     else if(gameMode == IcsPlayingBlack)
11770                         PlayIcsLossSound();
11771                     break;
11772                 case BlackWins:
11773                     if (gameMode == IcsPlayingBlack)
11774                         PlayIcsWinSound();
11775                     else if(gameMode == IcsPlayingWhite)
11776                         PlayIcsLossSound();
11777                     break;
11778                 case GameIsDrawn:
11779                     PlayIcsDrawSound();
11780                     break;
11781                 default:
11782                     PlayIcsUnfinishedSound();
11783                 }
11784             }
11785             if(appData.quitNext) { ExitEvent(0); return; }
11786         } else if (gameMode == EditGame ||
11787                    gameMode == PlayFromGameFile ||
11788                    gameMode == AnalyzeMode ||
11789                    gameMode == AnalyzeFile) {
11790             nextGameMode = gameMode;
11791         } else {
11792             nextGameMode = EndOfGame;
11793         }
11794         pausing = FALSE;
11795         ModeHighlight();
11796     } else {
11797         nextGameMode = gameMode;
11798     }
11799
11800     if (appData.noChessProgram) {
11801         gameMode = nextGameMode;
11802         ModeHighlight();
11803         endingGame = 0; /* [HGM] crash */
11804         return;
11805     }
11806
11807     if (first.reuse) {
11808         /* Put first chess program into idle state */
11809         if (first.pr != NoProc &&
11810             (gameMode == MachinePlaysWhite ||
11811              gameMode == MachinePlaysBlack ||
11812              gameMode == TwoMachinesPlay ||
11813              gameMode == IcsPlayingWhite ||
11814              gameMode == IcsPlayingBlack ||
11815              gameMode == BeginningOfGame)) {
11816             SendToProgram("force\n", &first);
11817             if (first.usePing) {
11818               char buf[MSG_SIZ];
11819               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11820               SendToProgram(buf, &first);
11821             }
11822         }
11823     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11824         /* Kill off first chess program */
11825         if (first.isr != NULL)
11826           RemoveInputSource(first.isr);
11827         first.isr = NULL;
11828
11829         if (first.pr != NoProc) {
11830             ExitAnalyzeMode();
11831             DoSleep( appData.delayBeforeQuit );
11832             SendToProgram("quit\n", &first);
11833             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11834             first.reload = TRUE;
11835         }
11836         first.pr = NoProc;
11837     }
11838     if (second.reuse) {
11839         /* Put second chess program into idle state */
11840         if (second.pr != NoProc &&
11841             gameMode == TwoMachinesPlay) {
11842             SendToProgram("force\n", &second);
11843             if (second.usePing) {
11844               char buf[MSG_SIZ];
11845               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11846               SendToProgram(buf, &second);
11847             }
11848         }
11849     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11850         /* Kill off second chess program */
11851         if (second.isr != NULL)
11852           RemoveInputSource(second.isr);
11853         second.isr = NULL;
11854
11855         if (second.pr != NoProc) {
11856             DoSleep( appData.delayBeforeQuit );
11857             SendToProgram("quit\n", &second);
11858             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11859             second.reload = TRUE;
11860         }
11861         second.pr = NoProc;
11862     }
11863
11864     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11865         char resChar = '=';
11866         switch (result) {
11867         case WhiteWins:
11868           resChar = '+';
11869           if (first.twoMachinesColor[0] == 'w') {
11870             first.matchWins++;
11871           } else {
11872             second.matchWins++;
11873           }
11874           break;
11875         case BlackWins:
11876           resChar = '-';
11877           if (first.twoMachinesColor[0] == 'b') {
11878             first.matchWins++;
11879           } else {
11880             second.matchWins++;
11881           }
11882           break;
11883         case GameUnfinished:
11884           resChar = ' ';
11885         default:
11886           break;
11887         }
11888
11889         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11890         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11891             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11892             ReserveGame(nextGame, resChar); // sets nextGame
11893             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11894             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11895         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11896
11897         if (nextGame <= appData.matchGames && !abortMatch) {
11898             gameMode = nextGameMode;
11899             matchGame = nextGame; // this will be overruled in tourney mode!
11900             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11901             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11902             endingGame = 0; /* [HGM] crash */
11903             return;
11904         } else {
11905             gameMode = nextGameMode;
11906             if(appData.epd) {
11907                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11908                 OutputKibitz(2, buf);
11909                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11910                 OutputKibitz(2, buf);
11911                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11912                 if(second.matchWins) OutputKibitz(2, buf);
11913                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11914                 OutputKibitz(2, buf);
11915             }
11916             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11917                      first.tidy, second.tidy,
11918                      first.matchWins, second.matchWins,
11919                      appData.matchGames - (first.matchWins + second.matchWins));
11920             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11921             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11922             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11923             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11924                 first.twoMachinesColor = "black\n";
11925                 second.twoMachinesColor = "white\n";
11926             } else {
11927                 first.twoMachinesColor = "white\n";
11928                 second.twoMachinesColor = "black\n";
11929             }
11930         }
11931     }
11932     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11933         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11934       ExitAnalyzeMode();
11935     gameMode = nextGameMode;
11936     ModeHighlight();
11937     endingGame = 0;  /* [HGM] crash */
11938     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11939         if(matchMode == TRUE) { // match through command line: exit with or without popup
11940             if(ranking) {
11941                 ToNrEvent(forwardMostMove);
11942                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11943                 else ExitEvent(0);
11944             } else DisplayFatalError(buf, 0, 0);
11945         } else { // match through menu; just stop, with or without popup
11946             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11947             ModeHighlight();
11948             if(ranking){
11949                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11950             } else DisplayNote(buf);
11951       }
11952       if(ranking) free(ranking);
11953     }
11954 }
11955
11956 /* Assumes program was just initialized (initString sent).
11957    Leaves program in force mode. */
11958 void
11959 FeedMovesToProgram (ChessProgramState *cps, int upto)
11960 {
11961     int i;
11962
11963     if (appData.debugMode)
11964       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11965               startedFromSetupPosition ? "position and " : "",
11966               backwardMostMove, upto, cps->which);
11967     if(currentlyInitializedVariant != gameInfo.variant) {
11968       char buf[MSG_SIZ];
11969         // [HGM] variantswitch: make engine aware of new variant
11970         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11971                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11972                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11973         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11974         SendToProgram(buf, cps);
11975         currentlyInitializedVariant = gameInfo.variant;
11976     }
11977     SendToProgram("force\n", cps);
11978     if (startedFromSetupPosition) {
11979         SendBoard(cps, backwardMostMove);
11980     if (appData.debugMode) {
11981         fprintf(debugFP, "feedMoves\n");
11982     }
11983     }
11984     for (i = backwardMostMove; i < upto; i++) {
11985         SendMoveToProgram(i, cps);
11986     }
11987 }
11988
11989
11990 int
11991 ResurrectChessProgram ()
11992 {
11993      /* The chess program may have exited.
11994         If so, restart it and feed it all the moves made so far. */
11995     static int doInit = 0;
11996
11997     if (appData.noChessProgram) return 1;
11998
11999     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12000         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12001         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12002         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12003     } else {
12004         if (first.pr != NoProc) return 1;
12005         StartChessProgram(&first);
12006     }
12007     InitChessProgram(&first, FALSE);
12008     FeedMovesToProgram(&first, currentMove);
12009
12010     if (!first.sendTime) {
12011         /* can't tell gnuchess what its clock should read,
12012            so we bow to its notion. */
12013         ResetClocks();
12014         timeRemaining[0][currentMove] = whiteTimeRemaining;
12015         timeRemaining[1][currentMove] = blackTimeRemaining;
12016     }
12017
12018     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12019                 appData.icsEngineAnalyze) && first.analysisSupport) {
12020       SendToProgram("analyze\n", &first);
12021       first.analyzing = TRUE;
12022     }
12023     return 1;
12024 }
12025
12026 /*
12027  * Button procedures
12028  */
12029 void
12030 Reset (int redraw, int init)
12031 {
12032     int i;
12033
12034     if (appData.debugMode) {
12035         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12036                 redraw, init, gameMode);
12037     }
12038     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12039     deadRanks = 0; // assume entire board is used
12040     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12041     CleanupTail(); // [HGM] vari: delete any stored variations
12042     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12043     pausing = pauseExamInvalid = FALSE;
12044     startedFromSetupPosition = blackPlaysFirst = FALSE;
12045     firstMove = TRUE;
12046     whiteFlag = blackFlag = FALSE;
12047     userOfferedDraw = FALSE;
12048     hintRequested = bookRequested = FALSE;
12049     first.maybeThinking = FALSE;
12050     second.maybeThinking = FALSE;
12051     first.bookSuspend = FALSE; // [HGM] book
12052     second.bookSuspend = FALSE;
12053     thinkOutput[0] = NULLCHAR;
12054     lastHint[0] = NULLCHAR;
12055     ClearGameInfo(&gameInfo);
12056     gameInfo.variant = StringToVariant(appData.variant);
12057     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12058         gameInfo.variant = VariantUnknown;
12059         strncpy(engineVariant, appData.variant, MSG_SIZ);
12060     }
12061     ics_user_moved = ics_clock_paused = FALSE;
12062     ics_getting_history = H_FALSE;
12063     ics_gamenum = -1;
12064     white_holding[0] = black_holding[0] = NULLCHAR;
12065     ClearProgramStats();
12066     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12067
12068     ResetFrontEnd();
12069     ClearHighlights();
12070     flipView = appData.flipView;
12071     ClearPremoveHighlights();
12072     gotPremove = FALSE;
12073     alarmSounded = FALSE;
12074     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12075
12076     GameEnds(EndOfFile, NULL, GE_PLAYER);
12077     if(appData.serverMovesName != NULL) {
12078         /* [HGM] prepare to make moves file for broadcasting */
12079         clock_t t = clock();
12080         if(serverMoves != NULL) fclose(serverMoves);
12081         serverMoves = fopen(appData.serverMovesName, "r");
12082         if(serverMoves != NULL) {
12083             fclose(serverMoves);
12084             /* delay 15 sec before overwriting, so all clients can see end */
12085             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12086         }
12087         serverMoves = fopen(appData.serverMovesName, "w");
12088     }
12089
12090     ExitAnalyzeMode();
12091     gameMode = BeginningOfGame;
12092     ModeHighlight();
12093     if(appData.icsActive) gameInfo.variant = VariantNormal;
12094     currentMove = forwardMostMove = backwardMostMove = 0;
12095     MarkTargetSquares(1);
12096     InitPosition(redraw);
12097     for (i = 0; i < MAX_MOVES; i++) {
12098         if (commentList[i] != NULL) {
12099             free(commentList[i]);
12100             commentList[i] = NULL;
12101         }
12102     }
12103     ResetClocks();
12104     timeRemaining[0][0] = whiteTimeRemaining;
12105     timeRemaining[1][0] = blackTimeRemaining;
12106
12107     if (first.pr == NoProc) {
12108         StartChessProgram(&first);
12109     }
12110     if (init) {
12111             InitChessProgram(&first, startedFromSetupPosition);
12112     }
12113     DisplayTitle("");
12114     DisplayMessage("", "");
12115     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12116     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12117     ClearMap();        // [HGM] exclude: invalidate map
12118 }
12119
12120 void
12121 AutoPlayGameLoop ()
12122 {
12123     for (;;) {
12124         if (!AutoPlayOneMove())
12125           return;
12126         if (matchMode || appData.timeDelay == 0)
12127           continue;
12128         if (appData.timeDelay < 0)
12129           return;
12130         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12131         break;
12132     }
12133 }
12134
12135 void
12136 AnalyzeNextGame()
12137 {
12138     ReloadGame(1); // next game
12139 }
12140
12141 int
12142 AutoPlayOneMove ()
12143 {
12144     int fromX, fromY, toX, toY;
12145
12146     if (appData.debugMode) {
12147       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12148     }
12149
12150     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12151       return FALSE;
12152
12153     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12154       pvInfoList[currentMove].depth = programStats.depth;
12155       pvInfoList[currentMove].score = programStats.score;
12156       pvInfoList[currentMove].time  = 0;
12157       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12158       else { // append analysis of final position as comment
12159         char buf[MSG_SIZ];
12160         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12161         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12162       }
12163       programStats.depth = 0;
12164     }
12165
12166     if (currentMove >= forwardMostMove) {
12167       if(gameMode == AnalyzeFile) {
12168           if(appData.loadGameIndex == -1) {
12169             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12170           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12171           } else {
12172           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12173         }
12174       }
12175 //      gameMode = EndOfGame;
12176 //      ModeHighlight();
12177
12178       /* [AS] Clear current move marker at the end of a game */
12179       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12180
12181       return FALSE;
12182     }
12183
12184     toX = moveList[currentMove][2] - AAA;
12185     toY = moveList[currentMove][3] - ONE;
12186
12187     if (moveList[currentMove][1] == '@') {
12188         if (appData.highlightLastMove) {
12189             SetHighlights(-1, -1, toX, toY);
12190         }
12191     } else {
12192         fromX = moveList[currentMove][0] - AAA;
12193         fromY = moveList[currentMove][1] - ONE;
12194
12195         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12196
12197         if(moveList[currentMove][4] == ';') { // multi-leg
12198             killX = moveList[currentMove][5] - AAA;
12199             killY = moveList[currentMove][6] - ONE;
12200         }
12201         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12202         killX = killY = -1;
12203
12204         if (appData.highlightLastMove) {
12205             SetHighlights(fromX, fromY, toX, toY);
12206         }
12207     }
12208     DisplayMove(currentMove);
12209     SendMoveToProgram(currentMove++, &first);
12210     DisplayBothClocks();
12211     DrawPosition(FALSE, boards[currentMove]);
12212     // [HGM] PV info: always display, routine tests if empty
12213     DisplayComment(currentMove - 1, commentList[currentMove]);
12214     return TRUE;
12215 }
12216
12217
12218 int
12219 LoadGameOneMove (ChessMove readAhead)
12220 {
12221     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12222     char promoChar = NULLCHAR;
12223     ChessMove moveType;
12224     char move[MSG_SIZ];
12225     char *p, *q;
12226
12227     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12228         gameMode != AnalyzeMode && gameMode != Training) {
12229         gameFileFP = NULL;
12230         return FALSE;
12231     }
12232
12233     yyboardindex = forwardMostMove;
12234     if (readAhead != EndOfFile) {
12235       moveType = readAhead;
12236     } else {
12237       if (gameFileFP == NULL)
12238           return FALSE;
12239       moveType = (ChessMove) Myylex();
12240     }
12241
12242     done = FALSE;
12243     switch (moveType) {
12244       case Comment:
12245         if (appData.debugMode)
12246           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12247         p = yy_text;
12248
12249         /* append the comment but don't display it */
12250         AppendComment(currentMove, p, FALSE);
12251         return TRUE;
12252
12253       case WhiteCapturesEnPassant:
12254       case BlackCapturesEnPassant:
12255       case WhitePromotion:
12256       case BlackPromotion:
12257       case WhiteNonPromotion:
12258       case BlackNonPromotion:
12259       case NormalMove:
12260       case FirstLeg:
12261       case WhiteKingSideCastle:
12262       case WhiteQueenSideCastle:
12263       case BlackKingSideCastle:
12264       case BlackQueenSideCastle:
12265       case WhiteKingSideCastleWild:
12266       case WhiteQueenSideCastleWild:
12267       case BlackKingSideCastleWild:
12268       case BlackQueenSideCastleWild:
12269       /* PUSH Fabien */
12270       case WhiteHSideCastleFR:
12271       case WhiteASideCastleFR:
12272       case BlackHSideCastleFR:
12273       case BlackASideCastleFR:
12274       /* POP Fabien */
12275         if (appData.debugMode)
12276           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12277         fromX = currentMoveString[0] - AAA;
12278         fromY = currentMoveString[1] - ONE;
12279         toX = currentMoveString[2] - AAA;
12280         toY = currentMoveString[3] - ONE;
12281         promoChar = currentMoveString[4];
12282         if(promoChar == ';') promoChar = currentMoveString[7];
12283         break;
12284
12285       case WhiteDrop:
12286       case BlackDrop:
12287         if (appData.debugMode)
12288           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12289         fromX = moveType == WhiteDrop ?
12290           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12291         (int) CharToPiece(ToLower(currentMoveString[0]));
12292         fromY = DROP_RANK;
12293         toX = currentMoveString[2] - AAA;
12294         toY = currentMoveString[3] - ONE;
12295         break;
12296
12297       case WhiteWins:
12298       case BlackWins:
12299       case GameIsDrawn:
12300       case GameUnfinished:
12301         if (appData.debugMode)
12302           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12303         p = strchr(yy_text, '{');
12304         if (p == NULL) p = strchr(yy_text, '(');
12305         if (p == NULL) {
12306             p = yy_text;
12307             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12308         } else {
12309             q = strchr(p, *p == '{' ? '}' : ')');
12310             if (q != NULL) *q = NULLCHAR;
12311             p++;
12312         }
12313         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12314         GameEnds(moveType, p, GE_FILE);
12315         done = TRUE;
12316         if (cmailMsgLoaded) {
12317             ClearHighlights();
12318             flipView = WhiteOnMove(currentMove);
12319             if (moveType == GameUnfinished) flipView = !flipView;
12320             if (appData.debugMode)
12321               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12322         }
12323         break;
12324
12325       case EndOfFile:
12326         if (appData.debugMode)
12327           fprintf(debugFP, "Parser hit end of file\n");
12328         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12329           case MT_NONE:
12330           case MT_CHECK:
12331             break;
12332           case MT_CHECKMATE:
12333           case MT_STAINMATE:
12334             if (WhiteOnMove(currentMove)) {
12335                 GameEnds(BlackWins, "Black mates", GE_FILE);
12336             } else {
12337                 GameEnds(WhiteWins, "White mates", GE_FILE);
12338             }
12339             break;
12340           case MT_STALEMATE:
12341             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12342             break;
12343         }
12344         done = TRUE;
12345         break;
12346
12347       case MoveNumberOne:
12348         if (lastLoadGameStart == GNUChessGame) {
12349             /* GNUChessGames have numbers, but they aren't move numbers */
12350             if (appData.debugMode)
12351               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12352                       yy_text, (int) moveType);
12353             return LoadGameOneMove(EndOfFile); /* tail recursion */
12354         }
12355         /* else fall thru */
12356
12357       case XBoardGame:
12358       case GNUChessGame:
12359       case PGNTag:
12360         /* Reached start of next game in file */
12361         if (appData.debugMode)
12362           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12363         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12364           case MT_NONE:
12365           case MT_CHECK:
12366             break;
12367           case MT_CHECKMATE:
12368           case MT_STAINMATE:
12369             if (WhiteOnMove(currentMove)) {
12370                 GameEnds(BlackWins, "Black mates", GE_FILE);
12371             } else {
12372                 GameEnds(WhiteWins, "White mates", GE_FILE);
12373             }
12374             break;
12375           case MT_STALEMATE:
12376             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12377             break;
12378         }
12379         done = TRUE;
12380         break;
12381
12382       case PositionDiagram:     /* should not happen; ignore */
12383       case ElapsedTime:         /* ignore */
12384       case NAG:                 /* ignore */
12385         if (appData.debugMode)
12386           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12387                   yy_text, (int) moveType);
12388         return LoadGameOneMove(EndOfFile); /* tail recursion */
12389
12390       case IllegalMove:
12391         if (appData.testLegality) {
12392             if (appData.debugMode)
12393               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12394             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12395                     (forwardMostMove / 2) + 1,
12396                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12397             DisplayError(move, 0);
12398             done = TRUE;
12399         } else {
12400             if (appData.debugMode)
12401               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12402                       yy_text, currentMoveString);
12403             if(currentMoveString[1] == '@') {
12404                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12405                 fromY = DROP_RANK;
12406             } else {
12407                 fromX = currentMoveString[0] - AAA;
12408                 fromY = currentMoveString[1] - ONE;
12409             }
12410             toX = currentMoveString[2] - AAA;
12411             toY = currentMoveString[3] - ONE;
12412             promoChar = currentMoveString[4];
12413         }
12414         break;
12415
12416       case AmbiguousMove:
12417         if (appData.debugMode)
12418           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12419         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12420                 (forwardMostMove / 2) + 1,
12421                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12422         DisplayError(move, 0);
12423         done = TRUE;
12424         break;
12425
12426       default:
12427       case ImpossibleMove:
12428         if (appData.debugMode)
12429           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12430         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12431                 (forwardMostMove / 2) + 1,
12432                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12433         DisplayError(move, 0);
12434         done = TRUE;
12435         break;
12436     }
12437
12438     if (done) {
12439         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12440             DrawPosition(FALSE, boards[currentMove]);
12441             DisplayBothClocks();
12442             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12443               DisplayComment(currentMove - 1, commentList[currentMove]);
12444         }
12445         (void) StopLoadGameTimer();
12446         gameFileFP = NULL;
12447         cmailOldMove = forwardMostMove;
12448         return FALSE;
12449     } else {
12450         /* currentMoveString is set as a side-effect of yylex */
12451
12452         thinkOutput[0] = NULLCHAR;
12453         MakeMove(fromX, fromY, toX, toY, promoChar);
12454         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12455         currentMove = forwardMostMove;
12456         return TRUE;
12457     }
12458 }
12459
12460 /* Load the nth game from the given file */
12461 int
12462 LoadGameFromFile (char *filename, int n, char *title, int useList)
12463 {
12464     FILE *f;
12465     char buf[MSG_SIZ];
12466
12467     if (strcmp(filename, "-") == 0) {
12468         f = stdin;
12469         title = "stdin";
12470     } else {
12471         f = fopen(filename, "rb");
12472         if (f == NULL) {
12473           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12474             DisplayError(buf, errno);
12475             return FALSE;
12476         }
12477     }
12478     if (fseek(f, 0, 0) == -1) {
12479         /* f is not seekable; probably a pipe */
12480         useList = FALSE;
12481     }
12482     if (useList && n == 0) {
12483         int error = GameListBuild(f);
12484         if (error) {
12485             DisplayError(_("Cannot build game list"), error);
12486         } else if (!ListEmpty(&gameList) &&
12487                    ((ListGame *) gameList.tailPred)->number > 1) {
12488             GameListPopUp(f, title);
12489             return TRUE;
12490         }
12491         GameListDestroy();
12492         n = 1;
12493     }
12494     if (n == 0) n = 1;
12495     return LoadGame(f, n, title, FALSE);
12496 }
12497
12498
12499 void
12500 MakeRegisteredMove ()
12501 {
12502     int fromX, fromY, toX, toY;
12503     char promoChar;
12504     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12505         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12506           case CMAIL_MOVE:
12507           case CMAIL_DRAW:
12508             if (appData.debugMode)
12509               fprintf(debugFP, "Restoring %s for game %d\n",
12510                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12511
12512             thinkOutput[0] = NULLCHAR;
12513             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12514             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12515             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12516             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12517             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12518             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12519             MakeMove(fromX, fromY, toX, toY, promoChar);
12520             ShowMove(fromX, fromY, toX, toY);
12521
12522             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12523               case MT_NONE:
12524               case MT_CHECK:
12525                 break;
12526
12527               case MT_CHECKMATE:
12528               case MT_STAINMATE:
12529                 if (WhiteOnMove(currentMove)) {
12530                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12531                 } else {
12532                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12533                 }
12534                 break;
12535
12536               case MT_STALEMATE:
12537                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12538                 break;
12539             }
12540
12541             break;
12542
12543           case CMAIL_RESIGN:
12544             if (WhiteOnMove(currentMove)) {
12545                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12546             } else {
12547                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12548             }
12549             break;
12550
12551           case CMAIL_ACCEPT:
12552             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12553             break;
12554
12555           default:
12556             break;
12557         }
12558     }
12559
12560     return;
12561 }
12562
12563 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12564 int
12565 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12566 {
12567     int retVal;
12568
12569     if (gameNumber > nCmailGames) {
12570         DisplayError(_("No more games in this message"), 0);
12571         return FALSE;
12572     }
12573     if (f == lastLoadGameFP) {
12574         int offset = gameNumber - lastLoadGameNumber;
12575         if (offset == 0) {
12576             cmailMsg[0] = NULLCHAR;
12577             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12578                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12579                 nCmailMovesRegistered--;
12580             }
12581             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12582             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12583                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12584             }
12585         } else {
12586             if (! RegisterMove()) return FALSE;
12587         }
12588     }
12589
12590     retVal = LoadGame(f, gameNumber, title, useList);
12591
12592     /* Make move registered during previous look at this game, if any */
12593     MakeRegisteredMove();
12594
12595     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12596         commentList[currentMove]
12597           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12598         DisplayComment(currentMove - 1, commentList[currentMove]);
12599     }
12600
12601     return retVal;
12602 }
12603
12604 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12605 int
12606 ReloadGame (int offset)
12607 {
12608     int gameNumber = lastLoadGameNumber + offset;
12609     if (lastLoadGameFP == NULL) {
12610         DisplayError(_("No game has been loaded yet"), 0);
12611         return FALSE;
12612     }
12613     if (gameNumber <= 0) {
12614         DisplayError(_("Can't back up any further"), 0);
12615         return FALSE;
12616     }
12617     if (cmailMsgLoaded) {
12618         return CmailLoadGame(lastLoadGameFP, gameNumber,
12619                              lastLoadGameTitle, lastLoadGameUseList);
12620     } else {
12621         return LoadGame(lastLoadGameFP, gameNumber,
12622                         lastLoadGameTitle, lastLoadGameUseList);
12623     }
12624 }
12625
12626 int keys[EmptySquare+1];
12627
12628 int
12629 PositionMatches (Board b1, Board b2)
12630 {
12631     int r, f, sum=0;
12632     switch(appData.searchMode) {
12633         case 1: return CompareWithRights(b1, b2);
12634         case 2:
12635             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12636                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12637             }
12638             return TRUE;
12639         case 3:
12640             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12641               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12642                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12643             }
12644             return sum==0;
12645         case 4:
12646             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12647                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12648             }
12649             return sum==0;
12650     }
12651     return TRUE;
12652 }
12653
12654 #define Q_PROMO  4
12655 #define Q_EP     3
12656 #define Q_BCASTL 2
12657 #define Q_WCASTL 1
12658
12659 int pieceList[256], quickBoard[256];
12660 ChessSquare pieceType[256] = { EmptySquare };
12661 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12662 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12663 int soughtTotal, turn;
12664 Boolean epOK, flipSearch;
12665
12666 typedef struct {
12667     unsigned char piece, to;
12668 } Move;
12669
12670 #define DSIZE (250000)
12671
12672 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12673 Move *moveDatabase = initialSpace;
12674 unsigned int movePtr, dataSize = DSIZE;
12675
12676 int
12677 MakePieceList (Board board, int *counts)
12678 {
12679     int r, f, n=Q_PROMO, total=0;
12680     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12681     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12682         int sq = f + (r<<4);
12683         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12684             quickBoard[sq] = ++n;
12685             pieceList[n] = sq;
12686             pieceType[n] = board[r][f];
12687             counts[board[r][f]]++;
12688             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12689             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12690             total++;
12691         }
12692     }
12693     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12694     return total;
12695 }
12696
12697 void
12698 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12699 {
12700     int sq = fromX + (fromY<<4);
12701     int piece = quickBoard[sq], rook;
12702     quickBoard[sq] = 0;
12703     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12704     if(piece == pieceList[1] && 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;
12707         moveDatabase[movePtr++].piece = Q_WCASTL;
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] == WhiteRook) { // FRC castling
12712         quickBoard[sq] = 0; // remove Rook
12713         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12714         moveDatabase[movePtr++].piece = Q_WCASTL;
12715         quickBoard[sq] = pieceList[1]; // put King
12716         piece = rook;
12717         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12718       }
12719     } else
12720     if(piece == pieceList[2] && fromY == toY) {
12721       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12722         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12723         moveDatabase[movePtr++].piece = Q_BCASTL;
12724         quickBoard[sq] = piece;
12725         piece = quickBoard[from]; quickBoard[from] = 0;
12726         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12727       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12728         quickBoard[sq] = 0; // remove Rook
12729         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12730         moveDatabase[movePtr++].piece = Q_BCASTL;
12731         quickBoard[sq] = pieceList[2]; // put King
12732         piece = rook;
12733         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12734       }
12735     } else
12736     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12737         quickBoard[(fromY<<4)+toX] = 0;
12738         moveDatabase[movePtr].piece = Q_EP;
12739         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12740         moveDatabase[movePtr].to = sq;
12741     } else
12742     if(promoPiece != pieceType[piece]) {
12743         moveDatabase[movePtr++].piece = Q_PROMO;
12744         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12745     }
12746     moveDatabase[movePtr].piece = piece;
12747     quickBoard[sq] = piece;
12748     movePtr++;
12749 }
12750
12751 int
12752 PackGame (Board board)
12753 {
12754     Move *newSpace = NULL;
12755     moveDatabase[movePtr].piece = 0; // terminate previous game
12756     if(movePtr > dataSize) {
12757         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12758         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12759         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12760         if(newSpace) {
12761             int i;
12762             Move *p = moveDatabase, *q = newSpace;
12763             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12764             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12765             moveDatabase = newSpace;
12766         } else { // calloc failed, we must be out of memory. Too bad...
12767             dataSize = 0; // prevent calloc events for all subsequent games
12768             return 0;     // and signal this one isn't cached
12769         }
12770     }
12771     movePtr++;
12772     MakePieceList(board, counts);
12773     return movePtr;
12774 }
12775
12776 int
12777 QuickCompare (Board board, int *minCounts, int *maxCounts)
12778 {   // compare according to search mode
12779     int r, f;
12780     switch(appData.searchMode)
12781     {
12782       case 1: // exact position match
12783         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12784         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12785             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12786         }
12787         break;
12788       case 2: // can have extra material on empty squares
12789         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12790             if(board[r][f] == EmptySquare) continue;
12791             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12792         }
12793         break;
12794       case 3: // material with exact Pawn structure
12795         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12796             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12797             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12798         } // fall through to material comparison
12799       case 4: // exact material
12800         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12801         break;
12802       case 6: // material range with given imbalance
12803         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12804         // fall through to range comparison
12805       case 5: // material range
12806         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12807     }
12808     return TRUE;
12809 }
12810
12811 int
12812 QuickScan (Board board, Move *move)
12813 {   // reconstruct game,and compare all positions in it
12814     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12815     do {
12816         int piece = move->piece;
12817         int to = move->to, from = pieceList[piece];
12818         if(found < 0) { // if already found just scan to game end for final piece count
12819           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12820            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12821            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12822                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12823             ) {
12824             static int lastCounts[EmptySquare+1];
12825             int i;
12826             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12827             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12828           } else stretch = 0;
12829           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12830           if(found >= 0 && !appData.minPieces) return found;
12831         }
12832         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12833           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12834           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12835             piece = (++move)->piece;
12836             from = pieceList[piece];
12837             counts[pieceType[piece]]--;
12838             pieceType[piece] = (ChessSquare) move->to;
12839             counts[move->to]++;
12840           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12841             counts[pieceType[quickBoard[to]]]--;
12842             quickBoard[to] = 0; total--;
12843             move++;
12844             continue;
12845           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12846             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12847             from  = pieceList[piece]; // so this must be King
12848             quickBoard[from] = 0;
12849             pieceList[piece] = to;
12850             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12851             quickBoard[from] = 0; // rook
12852             quickBoard[to] = piece;
12853             to = move->to; piece = move->piece;
12854             goto aftercastle;
12855           }
12856         }
12857         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12858         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12859         quickBoard[from] = 0;
12860       aftercastle:
12861         quickBoard[to] = piece;
12862         pieceList[piece] = to;
12863         cnt++; turn ^= 3;
12864         move++;
12865     } while(1);
12866 }
12867
12868 void
12869 InitSearch ()
12870 {
12871     int r, f;
12872     flipSearch = FALSE;
12873     CopyBoard(soughtBoard, boards[currentMove]);
12874     soughtTotal = MakePieceList(soughtBoard, maxSought);
12875     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12876     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12877     CopyBoard(reverseBoard, boards[currentMove]);
12878     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12879         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12880         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12881         reverseBoard[r][f] = piece;
12882     }
12883     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12884     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12885     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12886                  || (boards[currentMove][CASTLING][2] == NoRights ||
12887                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12888                  && (boards[currentMove][CASTLING][5] == NoRights ||
12889                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12890       ) {
12891         flipSearch = TRUE;
12892         CopyBoard(flipBoard, soughtBoard);
12893         CopyBoard(rotateBoard, reverseBoard);
12894         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12895             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12896             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12897         }
12898     }
12899     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12900     if(appData.searchMode >= 5) {
12901         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12902         MakePieceList(soughtBoard, minSought);
12903         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12904     }
12905     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12906         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12907 }
12908
12909 GameInfo dummyInfo;
12910 static int creatingBook;
12911
12912 int
12913 GameContainsPosition (FILE *f, ListGame *lg)
12914 {
12915     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12916     int fromX, fromY, toX, toY;
12917     char promoChar;
12918     static int initDone=FALSE;
12919
12920     // weed out games based on numerical tag comparison
12921     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12922     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12923     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12924     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12925     if(!initDone) {
12926         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12927         initDone = TRUE;
12928     }
12929     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12930     else CopyBoard(boards[scratch], initialPosition); // default start position
12931     if(lg->moves) {
12932         turn = btm + 1;
12933         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12934         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12935     }
12936     if(btm) plyNr++;
12937     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12938     fseek(f, lg->offset, 0);
12939     yynewfile(f);
12940     while(1) {
12941         yyboardindex = scratch;
12942         quickFlag = plyNr+1;
12943         next = Myylex();
12944         quickFlag = 0;
12945         switch(next) {
12946             case PGNTag:
12947                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12948             default:
12949                 continue;
12950
12951             case XBoardGame:
12952             case GNUChessGame:
12953                 if(plyNr) return -1; // after we have seen moves, this is for new game
12954               continue;
12955
12956             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12957             case ImpossibleMove:
12958             case WhiteWins: // game ends here with these four
12959             case BlackWins:
12960             case GameIsDrawn:
12961             case GameUnfinished:
12962                 return -1;
12963
12964             case IllegalMove:
12965                 if(appData.testLegality) return -1;
12966             case WhiteCapturesEnPassant:
12967             case BlackCapturesEnPassant:
12968             case WhitePromotion:
12969             case BlackPromotion:
12970             case WhiteNonPromotion:
12971             case BlackNonPromotion:
12972             case NormalMove:
12973             case FirstLeg:
12974             case WhiteKingSideCastle:
12975             case WhiteQueenSideCastle:
12976             case BlackKingSideCastle:
12977             case BlackQueenSideCastle:
12978             case WhiteKingSideCastleWild:
12979             case WhiteQueenSideCastleWild:
12980             case BlackKingSideCastleWild:
12981             case BlackQueenSideCastleWild:
12982             case WhiteHSideCastleFR:
12983             case WhiteASideCastleFR:
12984             case BlackHSideCastleFR:
12985             case BlackASideCastleFR:
12986                 fromX = currentMoveString[0] - AAA;
12987                 fromY = currentMoveString[1] - ONE;
12988                 toX = currentMoveString[2] - AAA;
12989                 toY = currentMoveString[3] - ONE;
12990                 promoChar = currentMoveString[4];
12991                 break;
12992             case WhiteDrop:
12993             case BlackDrop:
12994                 fromX = next == WhiteDrop ?
12995                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12996                   (int) CharToPiece(ToLower(currentMoveString[0]));
12997                 fromY = DROP_RANK;
12998                 toX = currentMoveString[2] - AAA;
12999                 toY = currentMoveString[3] - ONE;
13000                 promoChar = 0;
13001                 break;
13002         }
13003         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13004         plyNr++;
13005         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13006         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13007         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13008         if(appData.findMirror) {
13009             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13010             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13011         }
13012     }
13013 }
13014
13015 /* Load the nth game from open file f */
13016 int
13017 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13018 {
13019     ChessMove cm;
13020     char buf[MSG_SIZ];
13021     int gn = gameNumber;
13022     ListGame *lg = NULL;
13023     int numPGNTags = 0, i;
13024     int err, pos = -1;
13025     GameMode oldGameMode;
13026     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13027     char oldName[MSG_SIZ];
13028
13029     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13030
13031     if (appData.debugMode)
13032         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13033
13034     if (gameMode == Training )
13035         SetTrainingModeOff();
13036
13037     oldGameMode = gameMode;
13038     if (gameMode != BeginningOfGame) {
13039       Reset(FALSE, TRUE);
13040     }
13041     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13042
13043     gameFileFP = f;
13044     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13045         fclose(lastLoadGameFP);
13046     }
13047
13048     if (useList) {
13049         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13050
13051         if (lg) {
13052             fseek(f, lg->offset, 0);
13053             GameListHighlight(gameNumber);
13054             pos = lg->position;
13055             gn = 1;
13056         }
13057         else {
13058             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13059               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13060             else
13061             DisplayError(_("Game number out of range"), 0);
13062             return FALSE;
13063         }
13064     } else {
13065         GameListDestroy();
13066         if (fseek(f, 0, 0) == -1) {
13067             if (f == lastLoadGameFP ?
13068                 gameNumber == lastLoadGameNumber + 1 :
13069                 gameNumber == 1) {
13070                 gn = 1;
13071             } else {
13072                 DisplayError(_("Can't seek on game file"), 0);
13073                 return FALSE;
13074             }
13075         }
13076     }
13077     lastLoadGameFP = f;
13078     lastLoadGameNumber = gameNumber;
13079     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13080     lastLoadGameUseList = useList;
13081
13082     yynewfile(f);
13083
13084     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13085       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13086                 lg->gameInfo.black);
13087             DisplayTitle(buf);
13088     } else if (*title != NULLCHAR) {
13089         if (gameNumber > 1) {
13090           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13091             DisplayTitle(buf);
13092         } else {
13093             DisplayTitle(title);
13094         }
13095     }
13096
13097     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13098         gameMode = PlayFromGameFile;
13099         ModeHighlight();
13100     }
13101
13102     currentMove = forwardMostMove = backwardMostMove = 0;
13103     CopyBoard(boards[0], initialPosition);
13104     StopClocks();
13105
13106     /*
13107      * Skip the first gn-1 games in the file.
13108      * Also skip over anything that precedes an identifiable
13109      * start of game marker, to avoid being confused by
13110      * garbage at the start of the file.  Currently
13111      * recognized start of game markers are the move number "1",
13112      * the pattern "gnuchess .* game", the pattern
13113      * "^[#;%] [^ ]* game file", and a PGN tag block.
13114      * A game that starts with one of the latter two patterns
13115      * will also have a move number 1, possibly
13116      * following a position diagram.
13117      * 5-4-02: Let's try being more lenient and allowing a game to
13118      * start with an unnumbered move.  Does that break anything?
13119      */
13120     cm = lastLoadGameStart = EndOfFile;
13121     while (gn > 0) {
13122         yyboardindex = forwardMostMove;
13123         cm = (ChessMove) Myylex();
13124         switch (cm) {
13125           case EndOfFile:
13126             if (cmailMsgLoaded) {
13127                 nCmailGames = CMAIL_MAX_GAMES - gn;
13128             } else {
13129                 Reset(TRUE, TRUE);
13130                 DisplayError(_("Game not found in file"), 0);
13131             }
13132             return FALSE;
13133
13134           case GNUChessGame:
13135           case XBoardGame:
13136             gn--;
13137             lastLoadGameStart = cm;
13138             break;
13139
13140           case MoveNumberOne:
13141             switch (lastLoadGameStart) {
13142               case GNUChessGame:
13143               case XBoardGame:
13144               case PGNTag:
13145                 break;
13146               case MoveNumberOne:
13147               case EndOfFile:
13148                 gn--;           /* count this game */
13149                 lastLoadGameStart = cm;
13150                 break;
13151               default:
13152                 /* impossible */
13153                 break;
13154             }
13155             break;
13156
13157           case PGNTag:
13158             switch (lastLoadGameStart) {
13159               case GNUChessGame:
13160               case PGNTag:
13161               case MoveNumberOne:
13162               case EndOfFile:
13163                 gn--;           /* count this game */
13164                 lastLoadGameStart = cm;
13165                 break;
13166               case XBoardGame:
13167                 lastLoadGameStart = cm; /* game counted already */
13168                 break;
13169               default:
13170                 /* impossible */
13171                 break;
13172             }
13173             if (gn > 0) {
13174                 do {
13175                     yyboardindex = forwardMostMove;
13176                     cm = (ChessMove) Myylex();
13177                 } while (cm == PGNTag || cm == Comment);
13178             }
13179             break;
13180
13181           case WhiteWins:
13182           case BlackWins:
13183           case GameIsDrawn:
13184             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13185                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13186                     != CMAIL_OLD_RESULT) {
13187                     nCmailResults ++ ;
13188                     cmailResult[  CMAIL_MAX_GAMES
13189                                 - gn - 1] = CMAIL_OLD_RESULT;
13190                 }
13191             }
13192             break;
13193
13194           case NormalMove:
13195           case FirstLeg:
13196             /* Only a NormalMove can be at the start of a game
13197              * without a position diagram. */
13198             if (lastLoadGameStart == EndOfFile ) {
13199               gn--;
13200               lastLoadGameStart = MoveNumberOne;
13201             }
13202             break;
13203
13204           default:
13205             break;
13206         }
13207     }
13208
13209     if (appData.debugMode)
13210       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13211
13212     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13213
13214     if (cm == XBoardGame) {
13215         /* Skip any header junk before position diagram and/or move 1 */
13216         for (;;) {
13217             yyboardindex = forwardMostMove;
13218             cm = (ChessMove) Myylex();
13219
13220             if (cm == EndOfFile ||
13221                 cm == GNUChessGame || cm == XBoardGame) {
13222                 /* Empty game; pretend end-of-file and handle later */
13223                 cm = EndOfFile;
13224                 break;
13225             }
13226
13227             if (cm == MoveNumberOne || cm == PositionDiagram ||
13228                 cm == PGNTag || cm == Comment)
13229               break;
13230         }
13231     } else if (cm == GNUChessGame) {
13232         if (gameInfo.event != NULL) {
13233             free(gameInfo.event);
13234         }
13235         gameInfo.event = StrSave(yy_text);
13236     }
13237
13238     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13239     while (cm == PGNTag) {
13240         if (appData.debugMode)
13241           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13242         err = ParsePGNTag(yy_text, &gameInfo);
13243         if (!err) numPGNTags++;
13244
13245         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13246         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13247             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13248             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13249             InitPosition(TRUE);
13250             oldVariant = gameInfo.variant;
13251             if (appData.debugMode)
13252               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13253         }
13254
13255
13256         if (gameInfo.fen != NULL) {
13257           Board initial_position;
13258           startedFromSetupPosition = TRUE;
13259           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13260             Reset(TRUE, TRUE);
13261             DisplayError(_("Bad FEN position in file"), 0);
13262             return FALSE;
13263           }
13264           CopyBoard(boards[0], initial_position);
13265           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13266             CopyBoard(initialPosition, initial_position);
13267           if (blackPlaysFirst) {
13268             currentMove = forwardMostMove = backwardMostMove = 1;
13269             CopyBoard(boards[1], initial_position);
13270             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13271             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13272             timeRemaining[0][1] = whiteTimeRemaining;
13273             timeRemaining[1][1] = blackTimeRemaining;
13274             if (commentList[0] != NULL) {
13275               commentList[1] = commentList[0];
13276               commentList[0] = NULL;
13277             }
13278           } else {
13279             currentMove = forwardMostMove = backwardMostMove = 0;
13280           }
13281           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13282           {   int i;
13283               initialRulePlies = FENrulePlies;
13284               for( i=0; i< nrCastlingRights; i++ )
13285                   initialRights[i] = initial_position[CASTLING][i];
13286           }
13287           yyboardindex = forwardMostMove;
13288           free(gameInfo.fen);
13289           gameInfo.fen = NULL;
13290         }
13291
13292         yyboardindex = forwardMostMove;
13293         cm = (ChessMove) Myylex();
13294
13295         /* Handle comments interspersed among the tags */
13296         while (cm == Comment) {
13297             char *p;
13298             if (appData.debugMode)
13299               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13300             p = yy_text;
13301             AppendComment(currentMove, p, FALSE);
13302             yyboardindex = forwardMostMove;
13303             cm = (ChessMove) Myylex();
13304         }
13305     }
13306
13307     /* don't rely on existence of Event tag since if game was
13308      * pasted from clipboard the Event tag may not exist
13309      */
13310     if (numPGNTags > 0){
13311         char *tags;
13312         if (gameInfo.variant == VariantNormal) {
13313           VariantClass v = StringToVariant(gameInfo.event);
13314           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13315           if(v < VariantShogi) gameInfo.variant = v;
13316         }
13317         if (!matchMode) {
13318           if( appData.autoDisplayTags ) {
13319             tags = PGNTags(&gameInfo);
13320             TagsPopUp(tags, CmailMsg());
13321             free(tags);
13322           }
13323         }
13324     } else {
13325         /* Make something up, but don't display it now */
13326         SetGameInfo();
13327         TagsPopDown();
13328     }
13329
13330     if (cm == PositionDiagram) {
13331         int i, j;
13332         char *p;
13333         Board initial_position;
13334
13335         if (appData.debugMode)
13336           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13337
13338         if (!startedFromSetupPosition) {
13339             p = yy_text;
13340             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13341               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13342                 switch (*p) {
13343                   case '{':
13344                   case '[':
13345                   case '-':
13346                   case ' ':
13347                   case '\t':
13348                   case '\n':
13349                   case '\r':
13350                     break;
13351                   default:
13352                     initial_position[i][j++] = CharToPiece(*p);
13353                     break;
13354                 }
13355             while (*p == ' ' || *p == '\t' ||
13356                    *p == '\n' || *p == '\r') p++;
13357
13358             if (strncmp(p, "black", strlen("black"))==0)
13359               blackPlaysFirst = TRUE;
13360             else
13361               blackPlaysFirst = FALSE;
13362             startedFromSetupPosition = TRUE;
13363
13364             CopyBoard(boards[0], initial_position);
13365             if (blackPlaysFirst) {
13366                 currentMove = forwardMostMove = backwardMostMove = 1;
13367                 CopyBoard(boards[1], initial_position);
13368                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13369                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13370                 timeRemaining[0][1] = whiteTimeRemaining;
13371                 timeRemaining[1][1] = blackTimeRemaining;
13372                 if (commentList[0] != NULL) {
13373                     commentList[1] = commentList[0];
13374                     commentList[0] = NULL;
13375                 }
13376             } else {
13377                 currentMove = forwardMostMove = backwardMostMove = 0;
13378             }
13379         }
13380         yyboardindex = forwardMostMove;
13381         cm = (ChessMove) Myylex();
13382     }
13383
13384   if(!creatingBook) {
13385     if (first.pr == NoProc) {
13386         StartChessProgram(&first);
13387     }
13388     InitChessProgram(&first, FALSE);
13389     if(gameInfo.variant == VariantUnknown && *oldName) {
13390         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13391         gameInfo.variant = v;
13392     }
13393     SendToProgram("force\n", &first);
13394     if (startedFromSetupPosition) {
13395         SendBoard(&first, forwardMostMove);
13396     if (appData.debugMode) {
13397         fprintf(debugFP, "Load Game\n");
13398     }
13399         DisplayBothClocks();
13400     }
13401   }
13402
13403     /* [HGM] server: flag to write setup moves in broadcast file as one */
13404     loadFlag = appData.suppressLoadMoves;
13405
13406     while (cm == Comment) {
13407         char *p;
13408         if (appData.debugMode)
13409           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13410         p = yy_text;
13411         AppendComment(currentMove, p, FALSE);
13412         yyboardindex = forwardMostMove;
13413         cm = (ChessMove) Myylex();
13414     }
13415
13416     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13417         cm == WhiteWins || cm == BlackWins ||
13418         cm == GameIsDrawn || cm == GameUnfinished) {
13419         DisplayMessage("", _("No moves in game"));
13420         if (cmailMsgLoaded) {
13421             if (appData.debugMode)
13422               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13423             ClearHighlights();
13424             flipView = FALSE;
13425         }
13426         DrawPosition(FALSE, boards[currentMove]);
13427         DisplayBothClocks();
13428         gameMode = EditGame;
13429         ModeHighlight();
13430         gameFileFP = NULL;
13431         cmailOldMove = 0;
13432         return TRUE;
13433     }
13434
13435     // [HGM] PV info: routine tests if comment empty
13436     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13437         DisplayComment(currentMove - 1, commentList[currentMove]);
13438     }
13439     if (!matchMode && appData.timeDelay != 0)
13440       DrawPosition(FALSE, boards[currentMove]);
13441
13442     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13443       programStats.ok_to_send = 1;
13444     }
13445
13446     /* if the first token after the PGN tags is a move
13447      * and not move number 1, retrieve it from the parser
13448      */
13449     if (cm != MoveNumberOne)
13450         LoadGameOneMove(cm);
13451
13452     /* load the remaining moves from the file */
13453     while (LoadGameOneMove(EndOfFile)) {
13454       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13455       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13456     }
13457
13458     /* rewind to the start of the game */
13459     currentMove = backwardMostMove;
13460
13461     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13462
13463     if (oldGameMode == AnalyzeFile) {
13464       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13465       AnalyzeFileEvent();
13466     } else
13467     if (oldGameMode == AnalyzeMode) {
13468       AnalyzeFileEvent();
13469     }
13470
13471     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13472         long int w, b; // [HGM] adjourn: restore saved clock times
13473         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13474         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13475             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13476             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13477         }
13478     }
13479
13480     if(creatingBook) return TRUE;
13481     if (!matchMode && pos > 0) {
13482         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13483     } else
13484     if (matchMode || appData.timeDelay == 0) {
13485       ToEndEvent();
13486     } else if (appData.timeDelay > 0) {
13487       AutoPlayGameLoop();
13488     }
13489
13490     if (appData.debugMode)
13491         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13492
13493     loadFlag = 0; /* [HGM] true game starts */
13494     return TRUE;
13495 }
13496
13497 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13498 int
13499 ReloadPosition (int offset)
13500 {
13501     int positionNumber = lastLoadPositionNumber + offset;
13502     if (lastLoadPositionFP == NULL) {
13503         DisplayError(_("No position has been loaded yet"), 0);
13504         return FALSE;
13505     }
13506     if (positionNumber <= 0) {
13507         DisplayError(_("Can't back up any further"), 0);
13508         return FALSE;
13509     }
13510     return LoadPosition(lastLoadPositionFP, positionNumber,
13511                         lastLoadPositionTitle);
13512 }
13513
13514 /* Load the nth position from the given file */
13515 int
13516 LoadPositionFromFile (char *filename, int n, char *title)
13517 {
13518     FILE *f;
13519     char buf[MSG_SIZ];
13520
13521     if (strcmp(filename, "-") == 0) {
13522         return LoadPosition(stdin, n, "stdin");
13523     } else {
13524         f = fopen(filename, "rb");
13525         if (f == NULL) {
13526             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13527             DisplayError(buf, errno);
13528             return FALSE;
13529         } else {
13530             return LoadPosition(f, n, title);
13531         }
13532     }
13533 }
13534
13535 /* Load the nth position from the given open file, and close it */
13536 int
13537 LoadPosition (FILE *f, int positionNumber, char *title)
13538 {
13539     char *p, line[MSG_SIZ];
13540     Board initial_position;
13541     int i, j, fenMode, pn;
13542
13543     if (gameMode == Training )
13544         SetTrainingModeOff();
13545
13546     if (gameMode != BeginningOfGame) {
13547         Reset(FALSE, TRUE);
13548     }
13549     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13550         fclose(lastLoadPositionFP);
13551     }
13552     if (positionNumber == 0) positionNumber = 1;
13553     lastLoadPositionFP = f;
13554     lastLoadPositionNumber = positionNumber;
13555     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13556     if (first.pr == NoProc && !appData.noChessProgram) {
13557       StartChessProgram(&first);
13558       InitChessProgram(&first, FALSE);
13559     }
13560     pn = positionNumber;
13561     if (positionNumber < 0) {
13562         /* Negative position number means to seek to that byte offset */
13563         if (fseek(f, -positionNumber, 0) == -1) {
13564             DisplayError(_("Can't seek on position file"), 0);
13565             return FALSE;
13566         };
13567         pn = 1;
13568     } else {
13569         if (fseek(f, 0, 0) == -1) {
13570             if (f == lastLoadPositionFP ?
13571                 positionNumber == lastLoadPositionNumber + 1 :
13572                 positionNumber == 1) {
13573                 pn = 1;
13574             } else {
13575                 DisplayError(_("Can't seek on position file"), 0);
13576                 return FALSE;
13577             }
13578         }
13579     }
13580     /* See if this file is FEN or old-style xboard */
13581     if (fgets(line, MSG_SIZ, f) == NULL) {
13582         DisplayError(_("Position not found in file"), 0);
13583         return FALSE;
13584     }
13585     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13586     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13587
13588     if (pn >= 2) {
13589         if (fenMode || line[0] == '#') pn--;
13590         while (pn > 0) {
13591             /* skip positions before number pn */
13592             if (fgets(line, MSG_SIZ, f) == NULL) {
13593                 Reset(TRUE, TRUE);
13594                 DisplayError(_("Position not found in file"), 0);
13595                 return FALSE;
13596             }
13597             if (fenMode || line[0] == '#') pn--;
13598         }
13599     }
13600
13601     if (fenMode) {
13602         char *p;
13603         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13604             DisplayError(_("Bad FEN position in file"), 0);
13605             return FALSE;
13606         }
13607         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13608             sscanf(p+4, "%[^;]", bestMove);
13609         } else *bestMove = NULLCHAR;
13610         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13611             sscanf(p+4, "%[^;]", avoidMove);
13612         } else *avoidMove = NULLCHAR;
13613     } else {
13614         (void) fgets(line, MSG_SIZ, f);
13615         (void) fgets(line, MSG_SIZ, f);
13616
13617         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13618             (void) fgets(line, MSG_SIZ, f);
13619             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13620                 if (*p == ' ')
13621                   continue;
13622                 initial_position[i][j++] = CharToPiece(*p);
13623             }
13624         }
13625
13626         blackPlaysFirst = FALSE;
13627         if (!feof(f)) {
13628             (void) fgets(line, MSG_SIZ, f);
13629             if (strncmp(line, "black", strlen("black"))==0)
13630               blackPlaysFirst = TRUE;
13631         }
13632     }
13633     startedFromSetupPosition = TRUE;
13634
13635     CopyBoard(boards[0], initial_position);
13636     if (blackPlaysFirst) {
13637         currentMove = forwardMostMove = backwardMostMove = 1;
13638         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13639         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13640         CopyBoard(boards[1], initial_position);
13641         DisplayMessage("", _("Black to play"));
13642     } else {
13643         currentMove = forwardMostMove = backwardMostMove = 0;
13644         DisplayMessage("", _("White to play"));
13645     }
13646     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13647     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13648         SendToProgram("force\n", &first);
13649         SendBoard(&first, forwardMostMove);
13650     }
13651     if (appData.debugMode) {
13652 int i, j;
13653   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13654   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13655         fprintf(debugFP, "Load Position\n");
13656     }
13657
13658     if (positionNumber > 1) {
13659       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13660         DisplayTitle(line);
13661     } else {
13662         DisplayTitle(title);
13663     }
13664     gameMode = EditGame;
13665     ModeHighlight();
13666     ResetClocks();
13667     timeRemaining[0][1] = whiteTimeRemaining;
13668     timeRemaining[1][1] = blackTimeRemaining;
13669     DrawPosition(FALSE, boards[currentMove]);
13670
13671     return TRUE;
13672 }
13673
13674
13675 void
13676 CopyPlayerNameIntoFileName (char **dest, char *src)
13677 {
13678     while (*src != NULLCHAR && *src != ',') {
13679         if (*src == ' ') {
13680             *(*dest)++ = '_';
13681             src++;
13682         } else {
13683             *(*dest)++ = *src++;
13684         }
13685     }
13686 }
13687
13688 char *
13689 DefaultFileName (char *ext)
13690 {
13691     static char def[MSG_SIZ];
13692     char *p;
13693
13694     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13695         p = def;
13696         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13697         *p++ = '-';
13698         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13699         *p++ = '.';
13700         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13701     } else {
13702         def[0] = NULLCHAR;
13703     }
13704     return def;
13705 }
13706
13707 /* Save the current game to the given file */
13708 int
13709 SaveGameToFile (char *filename, int append)
13710 {
13711     FILE *f;
13712     char buf[MSG_SIZ];
13713     int result, i, t,tot=0;
13714
13715     if (strcmp(filename, "-") == 0) {
13716         return SaveGame(stdout, 0, NULL);
13717     } else {
13718         for(i=0; i<10; i++) { // upto 10 tries
13719              f = fopen(filename, append ? "a" : "w");
13720              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13721              if(f || errno != 13) break;
13722              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13723              tot += t;
13724         }
13725         if (f == NULL) {
13726             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13727             DisplayError(buf, errno);
13728             return FALSE;
13729         } else {
13730             safeStrCpy(buf, lastMsg, MSG_SIZ);
13731             DisplayMessage(_("Waiting for access to save file"), "");
13732             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13733             DisplayMessage(_("Saving game"), "");
13734             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13735             result = SaveGame(f, 0, NULL);
13736             DisplayMessage(buf, "");
13737             return result;
13738         }
13739     }
13740 }
13741
13742 char *
13743 SavePart (char *str)
13744 {
13745     static char buf[MSG_SIZ];
13746     char *p;
13747
13748     p = strchr(str, ' ');
13749     if (p == NULL) return str;
13750     strncpy(buf, str, p - str);
13751     buf[p - str] = NULLCHAR;
13752     return buf;
13753 }
13754
13755 #define PGN_MAX_LINE 75
13756
13757 #define PGN_SIDE_WHITE  0
13758 #define PGN_SIDE_BLACK  1
13759
13760 static int
13761 FindFirstMoveOutOfBook (int side)
13762 {
13763     int result = -1;
13764
13765     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13766         int index = backwardMostMove;
13767         int has_book_hit = 0;
13768
13769         if( (index % 2) != side ) {
13770             index++;
13771         }
13772
13773         while( index < forwardMostMove ) {
13774             /* Check to see if engine is in book */
13775             int depth = pvInfoList[index].depth;
13776             int score = pvInfoList[index].score;
13777             int in_book = 0;
13778
13779             if( depth <= 2 ) {
13780                 in_book = 1;
13781             }
13782             else if( score == 0 && depth == 63 ) {
13783                 in_book = 1; /* Zappa */
13784             }
13785             else if( score == 2 && depth == 99 ) {
13786                 in_book = 1; /* Abrok */
13787             }
13788
13789             has_book_hit += in_book;
13790
13791             if( ! in_book ) {
13792                 result = index;
13793
13794                 break;
13795             }
13796
13797             index += 2;
13798         }
13799     }
13800
13801     return result;
13802 }
13803
13804 void
13805 GetOutOfBookInfo (char * buf)
13806 {
13807     int oob[2];
13808     int i;
13809     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13810
13811     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13812     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13813
13814     *buf = '\0';
13815
13816     if( oob[0] >= 0 || oob[1] >= 0 ) {
13817         for( i=0; i<2; i++ ) {
13818             int idx = oob[i];
13819
13820             if( idx >= 0 ) {
13821                 if( i > 0 && oob[0] >= 0 ) {
13822                     strcat( buf, "   " );
13823                 }
13824
13825                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13826                 sprintf( buf+strlen(buf), "%s%.2f",
13827                     pvInfoList[idx].score >= 0 ? "+" : "",
13828                     pvInfoList[idx].score / 100.0 );
13829             }
13830         }
13831     }
13832 }
13833
13834 /* Save game in PGN style */
13835 static void
13836 SaveGamePGN2 (FILE *f)
13837 {
13838     int i, offset, linelen, newblock;
13839 //    char *movetext;
13840     char numtext[32];
13841     int movelen, numlen, blank;
13842     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13843
13844     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13845
13846     PrintPGNTags(f, &gameInfo);
13847
13848     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13849
13850     if (backwardMostMove > 0 || startedFromSetupPosition) {
13851         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13852         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13853         fprintf(f, "\n{--------------\n");
13854         PrintPosition(f, backwardMostMove);
13855         fprintf(f, "--------------}\n");
13856         free(fen);
13857     }
13858     else {
13859         /* [AS] Out of book annotation */
13860         if( appData.saveOutOfBookInfo ) {
13861             char buf[64];
13862
13863             GetOutOfBookInfo( buf );
13864
13865             if( buf[0] != '\0' ) {
13866                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13867             }
13868         }
13869
13870         fprintf(f, "\n");
13871     }
13872
13873     i = backwardMostMove;
13874     linelen = 0;
13875     newblock = TRUE;
13876
13877     while (i < forwardMostMove) {
13878         /* Print comments preceding this move */
13879         if (commentList[i] != NULL) {
13880             if (linelen > 0) fprintf(f, "\n");
13881             fprintf(f, "%s", commentList[i]);
13882             linelen = 0;
13883             newblock = TRUE;
13884         }
13885
13886         /* Format move number */
13887         if ((i % 2) == 0)
13888           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13889         else
13890           if (newblock)
13891             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13892           else
13893             numtext[0] = NULLCHAR;
13894
13895         numlen = strlen(numtext);
13896         newblock = FALSE;
13897
13898         /* Print move number */
13899         blank = linelen > 0 && numlen > 0;
13900         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13901             fprintf(f, "\n");
13902             linelen = 0;
13903             blank = 0;
13904         }
13905         if (blank) {
13906             fprintf(f, " ");
13907             linelen++;
13908         }
13909         fprintf(f, "%s", numtext);
13910         linelen += numlen;
13911
13912         /* Get move */
13913         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13914         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13915
13916         /* Print move */
13917         blank = linelen > 0 && movelen > 0;
13918         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13919             fprintf(f, "\n");
13920             linelen = 0;
13921             blank = 0;
13922         }
13923         if (blank) {
13924             fprintf(f, " ");
13925             linelen++;
13926         }
13927         fprintf(f, "%s", move_buffer);
13928         linelen += movelen;
13929
13930         /* [AS] Add PV info if present */
13931         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13932             /* [HGM] add time */
13933             char buf[MSG_SIZ]; int seconds;
13934
13935             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13936
13937             if( seconds <= 0)
13938               buf[0] = 0;
13939             else
13940               if( seconds < 30 )
13941                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13942               else
13943                 {
13944                   seconds = (seconds + 4)/10; // round to full seconds
13945                   if( seconds < 60 )
13946                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13947                   else
13948                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13949                 }
13950
13951             if(appData.cumulativeTimePGN) {
13952                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
13953             }
13954
13955             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13956                       pvInfoList[i].score >= 0 ? "+" : "",
13957                       pvInfoList[i].score / 100.0,
13958                       pvInfoList[i].depth,
13959                       buf );
13960
13961             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13962
13963             /* Print score/depth */
13964             blank = linelen > 0 && movelen > 0;
13965             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13966                 fprintf(f, "\n");
13967                 linelen = 0;
13968                 blank = 0;
13969             }
13970             if (blank) {
13971                 fprintf(f, " ");
13972                 linelen++;
13973             }
13974             fprintf(f, "%s", move_buffer);
13975             linelen += movelen;
13976         }
13977
13978         i++;
13979     }
13980
13981     /* Start a new line */
13982     if (linelen > 0) fprintf(f, "\n");
13983
13984     /* Print comments after last move */
13985     if (commentList[i] != NULL) {
13986         fprintf(f, "%s\n", commentList[i]);
13987     }
13988
13989     /* Print result */
13990     if (gameInfo.resultDetails != NULL &&
13991         gameInfo.resultDetails[0] != NULLCHAR) {
13992         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13993         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13994            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13995             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13996         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13997     } else {
13998         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13999     }
14000 }
14001
14002 /* Save game in PGN style and close the file */
14003 int
14004 SaveGamePGN (FILE *f)
14005 {
14006     SaveGamePGN2(f);
14007     fclose(f);
14008     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14009     return TRUE;
14010 }
14011
14012 /* Save game in old style and close the file */
14013 int
14014 SaveGameOldStyle (FILE *f)
14015 {
14016     int i, offset;
14017     time_t tm;
14018
14019     tm = time((time_t *) NULL);
14020
14021     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14022     PrintOpponents(f);
14023
14024     if (backwardMostMove > 0 || startedFromSetupPosition) {
14025         fprintf(f, "\n[--------------\n");
14026         PrintPosition(f, backwardMostMove);
14027         fprintf(f, "--------------]\n");
14028     } else {
14029         fprintf(f, "\n");
14030     }
14031
14032     i = backwardMostMove;
14033     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14034
14035     while (i < forwardMostMove) {
14036         if (commentList[i] != NULL) {
14037             fprintf(f, "[%s]\n", commentList[i]);
14038         }
14039
14040         if ((i % 2) == 1) {
14041             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14042             i++;
14043         } else {
14044             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14045             i++;
14046             if (commentList[i] != NULL) {
14047                 fprintf(f, "\n");
14048                 continue;
14049             }
14050             if (i >= forwardMostMove) {
14051                 fprintf(f, "\n");
14052                 break;
14053             }
14054             fprintf(f, "%s\n", parseList[i]);
14055             i++;
14056         }
14057     }
14058
14059     if (commentList[i] != NULL) {
14060         fprintf(f, "[%s]\n", commentList[i]);
14061     }
14062
14063     /* This isn't really the old style, but it's close enough */
14064     if (gameInfo.resultDetails != NULL &&
14065         gameInfo.resultDetails[0] != NULLCHAR) {
14066         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14067                 gameInfo.resultDetails);
14068     } else {
14069         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14070     }
14071
14072     fclose(f);
14073     return TRUE;
14074 }
14075
14076 /* Save the current game to open file f and close the file */
14077 int
14078 SaveGame (FILE *f, int dummy, char *dummy2)
14079 {
14080     if (gameMode == EditPosition) EditPositionDone(TRUE);
14081     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14082     if (appData.oldSaveStyle)
14083       return SaveGameOldStyle(f);
14084     else
14085       return SaveGamePGN(f);
14086 }
14087
14088 /* Save the current position to the given file */
14089 int
14090 SavePositionToFile (char *filename)
14091 {
14092     FILE *f;
14093     char buf[MSG_SIZ];
14094
14095     if (strcmp(filename, "-") == 0) {
14096         return SavePosition(stdout, 0, NULL);
14097     } else {
14098         f = fopen(filename, "a");
14099         if (f == NULL) {
14100             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14101             DisplayError(buf, errno);
14102             return FALSE;
14103         } else {
14104             safeStrCpy(buf, lastMsg, MSG_SIZ);
14105             DisplayMessage(_("Waiting for access to save file"), "");
14106             flock(fileno(f), LOCK_EX); // [HGM] lock
14107             DisplayMessage(_("Saving position"), "");
14108             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14109             SavePosition(f, 0, NULL);
14110             DisplayMessage(buf, "");
14111             return TRUE;
14112         }
14113     }
14114 }
14115
14116 /* Save the current position to the given open file and close the file */
14117 int
14118 SavePosition (FILE *f, int dummy, char *dummy2)
14119 {
14120     time_t tm;
14121     char *fen;
14122
14123     if (gameMode == EditPosition) EditPositionDone(TRUE);
14124     if (appData.oldSaveStyle) {
14125         tm = time((time_t *) NULL);
14126
14127         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14128         PrintOpponents(f);
14129         fprintf(f, "[--------------\n");
14130         PrintPosition(f, currentMove);
14131         fprintf(f, "--------------]\n");
14132     } else {
14133         fen = PositionToFEN(currentMove, NULL, 1);
14134         fprintf(f, "%s\n", fen);
14135         free(fen);
14136     }
14137     fclose(f);
14138     return TRUE;
14139 }
14140
14141 void
14142 ReloadCmailMsgEvent (int unregister)
14143 {
14144 #if !WIN32
14145     static char *inFilename = NULL;
14146     static char *outFilename;
14147     int i;
14148     struct stat inbuf, outbuf;
14149     int status;
14150
14151     /* Any registered moves are unregistered if unregister is set, */
14152     /* i.e. invoked by the signal handler */
14153     if (unregister) {
14154         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14155             cmailMoveRegistered[i] = FALSE;
14156             if (cmailCommentList[i] != NULL) {
14157                 free(cmailCommentList[i]);
14158                 cmailCommentList[i] = NULL;
14159             }
14160         }
14161         nCmailMovesRegistered = 0;
14162     }
14163
14164     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14165         cmailResult[i] = CMAIL_NOT_RESULT;
14166     }
14167     nCmailResults = 0;
14168
14169     if (inFilename == NULL) {
14170         /* Because the filenames are static they only get malloced once  */
14171         /* and they never get freed                                      */
14172         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14173         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14174
14175         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14176         sprintf(outFilename, "%s.out", appData.cmailGameName);
14177     }
14178
14179     status = stat(outFilename, &outbuf);
14180     if (status < 0) {
14181         cmailMailedMove = FALSE;
14182     } else {
14183         status = stat(inFilename, &inbuf);
14184         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14185     }
14186
14187     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14188        counts the games, notes how each one terminated, etc.
14189
14190        It would be nice to remove this kludge and instead gather all
14191        the information while building the game list.  (And to keep it
14192        in the game list nodes instead of having a bunch of fixed-size
14193        parallel arrays.)  Note this will require getting each game's
14194        termination from the PGN tags, as the game list builder does
14195        not process the game moves.  --mann
14196        */
14197     cmailMsgLoaded = TRUE;
14198     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14199
14200     /* Load first game in the file or popup game menu */
14201     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14202
14203 #endif /* !WIN32 */
14204     return;
14205 }
14206
14207 int
14208 RegisterMove ()
14209 {
14210     FILE *f;
14211     char string[MSG_SIZ];
14212
14213     if (   cmailMailedMove
14214         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14215         return TRUE;            /* Allow free viewing  */
14216     }
14217
14218     /* Unregister move to ensure that we don't leave RegisterMove        */
14219     /* with the move registered when the conditions for registering no   */
14220     /* longer hold                                                       */
14221     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14222         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14223         nCmailMovesRegistered --;
14224
14225         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14226           {
14227               free(cmailCommentList[lastLoadGameNumber - 1]);
14228               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14229           }
14230     }
14231
14232     if (cmailOldMove == -1) {
14233         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14234         return FALSE;
14235     }
14236
14237     if (currentMove > cmailOldMove + 1) {
14238         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14239         return FALSE;
14240     }
14241
14242     if (currentMove < cmailOldMove) {
14243         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14244         return FALSE;
14245     }
14246
14247     if (forwardMostMove > currentMove) {
14248         /* Silently truncate extra moves */
14249         TruncateGame();
14250     }
14251
14252     if (   (currentMove == cmailOldMove + 1)
14253         || (   (currentMove == cmailOldMove)
14254             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14255                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14256         if (gameInfo.result != GameUnfinished) {
14257             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14258         }
14259
14260         if (commentList[currentMove] != NULL) {
14261             cmailCommentList[lastLoadGameNumber - 1]
14262               = StrSave(commentList[currentMove]);
14263         }
14264         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14265
14266         if (appData.debugMode)
14267           fprintf(debugFP, "Saving %s for game %d\n",
14268                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14269
14270         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14271
14272         f = fopen(string, "w");
14273         if (appData.oldSaveStyle) {
14274             SaveGameOldStyle(f); /* also closes the file */
14275
14276             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14277             f = fopen(string, "w");
14278             SavePosition(f, 0, NULL); /* also closes the file */
14279         } else {
14280             fprintf(f, "{--------------\n");
14281             PrintPosition(f, currentMove);
14282             fprintf(f, "--------------}\n\n");
14283
14284             SaveGame(f, 0, NULL); /* also closes the file*/
14285         }
14286
14287         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14288         nCmailMovesRegistered ++;
14289     } else if (nCmailGames == 1) {
14290         DisplayError(_("You have not made a move yet"), 0);
14291         return FALSE;
14292     }
14293
14294     return TRUE;
14295 }
14296
14297 void
14298 MailMoveEvent ()
14299 {
14300 #if !WIN32
14301     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14302     FILE *commandOutput;
14303     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14304     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14305     int nBuffers;
14306     int i;
14307     int archived;
14308     char *arcDir;
14309
14310     if (! cmailMsgLoaded) {
14311         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14312         return;
14313     }
14314
14315     if (nCmailGames == nCmailResults) {
14316         DisplayError(_("No unfinished games"), 0);
14317         return;
14318     }
14319
14320 #if CMAIL_PROHIBIT_REMAIL
14321     if (cmailMailedMove) {
14322       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);
14323         DisplayError(msg, 0);
14324         return;
14325     }
14326 #endif
14327
14328     if (! (cmailMailedMove || RegisterMove())) return;
14329
14330     if (   cmailMailedMove
14331         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14332       snprintf(string, MSG_SIZ, partCommandString,
14333                appData.debugMode ? " -v" : "", appData.cmailGameName);
14334         commandOutput = popen(string, "r");
14335
14336         if (commandOutput == NULL) {
14337             DisplayError(_("Failed to invoke cmail"), 0);
14338         } else {
14339             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14340                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14341             }
14342             if (nBuffers > 1) {
14343                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14344                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14345                 nBytes = MSG_SIZ - 1;
14346             } else {
14347                 (void) memcpy(msg, buffer, nBytes);
14348             }
14349             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14350
14351             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14352                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14353
14354                 archived = TRUE;
14355                 for (i = 0; i < nCmailGames; i ++) {
14356                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14357                         archived = FALSE;
14358                     }
14359                 }
14360                 if (   archived
14361                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14362                         != NULL)) {
14363                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14364                            arcDir,
14365                            appData.cmailGameName,
14366                            gameInfo.date);
14367                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14368                     cmailMsgLoaded = FALSE;
14369                 }
14370             }
14371
14372             DisplayInformation(msg);
14373             pclose(commandOutput);
14374         }
14375     } else {
14376         if ((*cmailMsg) != '\0') {
14377             DisplayInformation(cmailMsg);
14378         }
14379     }
14380
14381     return;
14382 #endif /* !WIN32 */
14383 }
14384
14385 char *
14386 CmailMsg ()
14387 {
14388 #if WIN32
14389     return NULL;
14390 #else
14391     int  prependComma = 0;
14392     char number[5];
14393     char string[MSG_SIZ];       /* Space for game-list */
14394     int  i;
14395
14396     if (!cmailMsgLoaded) return "";
14397
14398     if (cmailMailedMove) {
14399       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14400     } else {
14401         /* Create a list of games left */
14402       snprintf(string, MSG_SIZ, "[");
14403         for (i = 0; i < nCmailGames; i ++) {
14404             if (! (   cmailMoveRegistered[i]
14405                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14406                 if (prependComma) {
14407                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14408                 } else {
14409                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14410                     prependComma = 1;
14411                 }
14412
14413                 strcat(string, number);
14414             }
14415         }
14416         strcat(string, "]");
14417
14418         if (nCmailMovesRegistered + nCmailResults == 0) {
14419             switch (nCmailGames) {
14420               case 1:
14421                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14422                 break;
14423
14424               case 2:
14425                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14426                 break;
14427
14428               default:
14429                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14430                          nCmailGames);
14431                 break;
14432             }
14433         } else {
14434             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14435               case 1:
14436                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14437                          string);
14438                 break;
14439
14440               case 0:
14441                 if (nCmailResults == nCmailGames) {
14442                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14443                 } else {
14444                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14445                 }
14446                 break;
14447
14448               default:
14449                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14450                          string);
14451             }
14452         }
14453     }
14454     return cmailMsg;
14455 #endif /* WIN32 */
14456 }
14457
14458 void
14459 ResetGameEvent ()
14460 {
14461     if (gameMode == Training)
14462       SetTrainingModeOff();
14463
14464     Reset(TRUE, TRUE);
14465     cmailMsgLoaded = FALSE;
14466     if (appData.icsActive) {
14467       SendToICS(ics_prefix);
14468       SendToICS("refresh\n");
14469     }
14470 }
14471
14472 void
14473 ExitEvent (int status)
14474 {
14475     exiting++;
14476     if (exiting > 2) {
14477       /* Give up on clean exit */
14478       exit(status);
14479     }
14480     if (exiting > 1) {
14481       /* Keep trying for clean exit */
14482       return;
14483     }
14484
14485     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14486     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14487
14488     if (telnetISR != NULL) {
14489       RemoveInputSource(telnetISR);
14490     }
14491     if (icsPR != NoProc) {
14492       DestroyChildProcess(icsPR, TRUE);
14493     }
14494
14495     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14496     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14497
14498     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14499     /* make sure this other one finishes before killing it!                  */
14500     if(endingGame) { int count = 0;
14501         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14502         while(endingGame && count++ < 10) DoSleep(1);
14503         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14504     }
14505
14506     /* Kill off chess programs */
14507     if (first.pr != NoProc) {
14508         ExitAnalyzeMode();
14509
14510         DoSleep( appData.delayBeforeQuit );
14511         SendToProgram("quit\n", &first);
14512         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14513     }
14514     if (second.pr != NoProc) {
14515         DoSleep( appData.delayBeforeQuit );
14516         SendToProgram("quit\n", &second);
14517         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14518     }
14519     if (first.isr != NULL) {
14520         RemoveInputSource(first.isr);
14521     }
14522     if (second.isr != NULL) {
14523         RemoveInputSource(second.isr);
14524     }
14525
14526     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14527     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14528
14529     ShutDownFrontEnd();
14530     exit(status);
14531 }
14532
14533 void
14534 PauseEngine (ChessProgramState *cps)
14535 {
14536     SendToProgram("pause\n", cps);
14537     cps->pause = 2;
14538 }
14539
14540 void
14541 UnPauseEngine (ChessProgramState *cps)
14542 {
14543     SendToProgram("resume\n", cps);
14544     cps->pause = 1;
14545 }
14546
14547 void
14548 PauseEvent ()
14549 {
14550     if (appData.debugMode)
14551         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14552     if (pausing) {
14553         pausing = FALSE;
14554         ModeHighlight();
14555         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14556             StartClocks();
14557             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14558                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14559                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14560             }
14561             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14562             HandleMachineMove(stashedInputMove, stalledEngine);
14563             stalledEngine = NULL;
14564             return;
14565         }
14566         if (gameMode == MachinePlaysWhite ||
14567             gameMode == TwoMachinesPlay   ||
14568             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14569             if(first.pause)  UnPauseEngine(&first);
14570             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14571             if(second.pause) UnPauseEngine(&second);
14572             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14573             StartClocks();
14574         } else {
14575             DisplayBothClocks();
14576         }
14577         if (gameMode == PlayFromGameFile) {
14578             if (appData.timeDelay >= 0)
14579                 AutoPlayGameLoop();
14580         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14581             Reset(FALSE, TRUE);
14582             SendToICS(ics_prefix);
14583             SendToICS("refresh\n");
14584         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14585             ForwardInner(forwardMostMove);
14586         }
14587         pauseExamInvalid = FALSE;
14588     } else {
14589         switch (gameMode) {
14590           default:
14591             return;
14592           case IcsExamining:
14593             pauseExamForwardMostMove = forwardMostMove;
14594             pauseExamInvalid = FALSE;
14595             /* fall through */
14596           case IcsObserving:
14597           case IcsPlayingWhite:
14598           case IcsPlayingBlack:
14599             pausing = TRUE;
14600             ModeHighlight();
14601             return;
14602           case PlayFromGameFile:
14603             (void) StopLoadGameTimer();
14604             pausing = TRUE;
14605             ModeHighlight();
14606             break;
14607           case BeginningOfGame:
14608             if (appData.icsActive) return;
14609             /* else fall through */
14610           case MachinePlaysWhite:
14611           case MachinePlaysBlack:
14612           case TwoMachinesPlay:
14613             if (forwardMostMove == 0)
14614               return;           /* don't pause if no one has moved */
14615             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14616                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14617                 if(onMove->pause) {           // thinking engine can be paused
14618                     PauseEngine(onMove);      // do it
14619                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14620                         PauseEngine(onMove->other);
14621                     else
14622                         SendToProgram("easy\n", onMove->other);
14623                     StopClocks();
14624                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14625             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14626                 if(first.pause) {
14627                     PauseEngine(&first);
14628                     StopClocks();
14629                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14630             } else { // human on move, pause pondering by either method
14631                 if(first.pause)
14632                     PauseEngine(&first);
14633                 else if(appData.ponderNextMove)
14634                     SendToProgram("easy\n", &first);
14635                 StopClocks();
14636             }
14637             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14638           case AnalyzeMode:
14639             pausing = TRUE;
14640             ModeHighlight();
14641             break;
14642         }
14643     }
14644 }
14645
14646 void
14647 EditCommentEvent ()
14648 {
14649     char title[MSG_SIZ];
14650
14651     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14652       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14653     } else {
14654       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14655                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14656                parseList[currentMove - 1]);
14657     }
14658
14659     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14660 }
14661
14662
14663 void
14664 EditTagsEvent ()
14665 {
14666     char *tags = PGNTags(&gameInfo);
14667     bookUp = FALSE;
14668     EditTagsPopUp(tags, NULL);
14669     free(tags);
14670 }
14671
14672 void
14673 ToggleSecond ()
14674 {
14675   if(second.analyzing) {
14676     SendToProgram("exit\n", &second);
14677     second.analyzing = FALSE;
14678   } else {
14679     if (second.pr == NoProc) StartChessProgram(&second);
14680     InitChessProgram(&second, FALSE);
14681     FeedMovesToProgram(&second, currentMove);
14682
14683     SendToProgram("analyze\n", &second);
14684     second.analyzing = TRUE;
14685   }
14686 }
14687
14688 /* Toggle ShowThinking */
14689 void
14690 ToggleShowThinking()
14691 {
14692   appData.showThinking = !appData.showThinking;
14693   ShowThinkingEvent();
14694 }
14695
14696 int
14697 AnalyzeModeEvent ()
14698 {
14699     char buf[MSG_SIZ];
14700
14701     if (!first.analysisSupport) {
14702       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14703       DisplayError(buf, 0);
14704       return 0;
14705     }
14706     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14707     if (appData.icsActive) {
14708         if (gameMode != IcsObserving) {
14709           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14710             DisplayError(buf, 0);
14711             /* secure check */
14712             if (appData.icsEngineAnalyze) {
14713                 if (appData.debugMode)
14714                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14715                 ExitAnalyzeMode();
14716                 ModeHighlight();
14717             }
14718             return 0;
14719         }
14720         /* if enable, user wants to disable icsEngineAnalyze */
14721         if (appData.icsEngineAnalyze) {
14722                 ExitAnalyzeMode();
14723                 ModeHighlight();
14724                 return 0;
14725         }
14726         appData.icsEngineAnalyze = TRUE;
14727         if (appData.debugMode)
14728             fprintf(debugFP, "ICS engine analyze starting... \n");
14729     }
14730
14731     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14732     if (appData.noChessProgram || gameMode == AnalyzeMode)
14733       return 0;
14734
14735     if (gameMode != AnalyzeFile) {
14736         if (!appData.icsEngineAnalyze) {
14737                EditGameEvent();
14738                if (gameMode != EditGame) return 0;
14739         }
14740         if (!appData.showThinking) ToggleShowThinking();
14741         ResurrectChessProgram();
14742         SendToProgram("analyze\n", &first);
14743         first.analyzing = TRUE;
14744         /*first.maybeThinking = TRUE;*/
14745         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14746         EngineOutputPopUp();
14747     }
14748     if (!appData.icsEngineAnalyze) {
14749         gameMode = AnalyzeMode;
14750         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14751     }
14752     pausing = FALSE;
14753     ModeHighlight();
14754     SetGameInfo();
14755
14756     StartAnalysisClock();
14757     GetTimeMark(&lastNodeCountTime);
14758     lastNodeCount = 0;
14759     return 1;
14760 }
14761
14762 void
14763 AnalyzeFileEvent ()
14764 {
14765     if (appData.noChessProgram || gameMode == AnalyzeFile)
14766       return;
14767
14768     if (!first.analysisSupport) {
14769       char buf[MSG_SIZ];
14770       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14771       DisplayError(buf, 0);
14772       return;
14773     }
14774
14775     if (gameMode != AnalyzeMode) {
14776         keepInfo = 1; // mere annotating should not alter PGN tags
14777         EditGameEvent();
14778         keepInfo = 0;
14779         if (gameMode != EditGame) return;
14780         if (!appData.showThinking) ToggleShowThinking();
14781         ResurrectChessProgram();
14782         SendToProgram("analyze\n", &first);
14783         first.analyzing = TRUE;
14784         /*first.maybeThinking = TRUE;*/
14785         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14786         EngineOutputPopUp();
14787     }
14788     gameMode = AnalyzeFile;
14789     pausing = FALSE;
14790     ModeHighlight();
14791
14792     StartAnalysisClock();
14793     GetTimeMark(&lastNodeCountTime);
14794     lastNodeCount = 0;
14795     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14796     AnalysisPeriodicEvent(1);
14797 }
14798
14799 void
14800 MachineWhiteEvent ()
14801 {
14802     char buf[MSG_SIZ];
14803     char *bookHit = NULL;
14804
14805     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14806       return;
14807
14808
14809     if (gameMode == PlayFromGameFile ||
14810         gameMode == TwoMachinesPlay  ||
14811         gameMode == Training         ||
14812         gameMode == AnalyzeMode      ||
14813         gameMode == EndOfGame)
14814         EditGameEvent();
14815
14816     if (gameMode == EditPosition)
14817         EditPositionDone(TRUE);
14818
14819     if (!WhiteOnMove(currentMove)) {
14820         DisplayError(_("It is not White's turn"), 0);
14821         return;
14822     }
14823
14824     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14825       ExitAnalyzeMode();
14826
14827     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14828         gameMode == AnalyzeFile)
14829         TruncateGame();
14830
14831     ResurrectChessProgram();    /* in case it isn't running */
14832     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14833         gameMode = MachinePlaysWhite;
14834         ResetClocks();
14835     } else
14836     gameMode = MachinePlaysWhite;
14837     pausing = FALSE;
14838     ModeHighlight();
14839     SetGameInfo();
14840     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14841     DisplayTitle(buf);
14842     if (first.sendName) {
14843       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14844       SendToProgram(buf, &first);
14845     }
14846     if (first.sendTime) {
14847       if (first.useColors) {
14848         SendToProgram("black\n", &first); /*gnu kludge*/
14849       }
14850       SendTimeRemaining(&first, TRUE);
14851     }
14852     if (first.useColors) {
14853       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14854     }
14855     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14856     SetMachineThinkingEnables();
14857     first.maybeThinking = TRUE;
14858     StartClocks();
14859     firstMove = FALSE;
14860
14861     if (appData.autoFlipView && !flipView) {
14862       flipView = !flipView;
14863       DrawPosition(FALSE, NULL);
14864       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14865     }
14866
14867     if(bookHit) { // [HGM] book: simulate book reply
14868         static char bookMove[MSG_SIZ]; // a bit generous?
14869
14870         programStats.nodes = programStats.depth = programStats.time =
14871         programStats.score = programStats.got_only_move = 0;
14872         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14873
14874         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14875         strcat(bookMove, bookHit);
14876         HandleMachineMove(bookMove, &first);
14877     }
14878 }
14879
14880 void
14881 MachineBlackEvent ()
14882 {
14883   char buf[MSG_SIZ];
14884   char *bookHit = NULL;
14885
14886     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14887         return;
14888
14889
14890     if (gameMode == PlayFromGameFile ||
14891         gameMode == TwoMachinesPlay  ||
14892         gameMode == Training         ||
14893         gameMode == AnalyzeMode      ||
14894         gameMode == EndOfGame)
14895         EditGameEvent();
14896
14897     if (gameMode == EditPosition)
14898         EditPositionDone(TRUE);
14899
14900     if (WhiteOnMove(currentMove)) {
14901         DisplayError(_("It is not Black's turn"), 0);
14902         return;
14903     }
14904
14905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14906       ExitAnalyzeMode();
14907
14908     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14909         gameMode == AnalyzeFile)
14910         TruncateGame();
14911
14912     ResurrectChessProgram();    /* in case it isn't running */
14913     gameMode = MachinePlaysBlack;
14914     pausing = FALSE;
14915     ModeHighlight();
14916     SetGameInfo();
14917     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14918     DisplayTitle(buf);
14919     if (first.sendName) {
14920       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14921       SendToProgram(buf, &first);
14922     }
14923     if (first.sendTime) {
14924       if (first.useColors) {
14925         SendToProgram("white\n", &first); /*gnu kludge*/
14926       }
14927       SendTimeRemaining(&first, FALSE);
14928     }
14929     if (first.useColors) {
14930       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14931     }
14932     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14933     SetMachineThinkingEnables();
14934     first.maybeThinking = TRUE;
14935     StartClocks();
14936
14937     if (appData.autoFlipView && flipView) {
14938       flipView = !flipView;
14939       DrawPosition(FALSE, NULL);
14940       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14941     }
14942     if(bookHit) { // [HGM] book: simulate book reply
14943         static char bookMove[MSG_SIZ]; // a bit generous?
14944
14945         programStats.nodes = programStats.depth = programStats.time =
14946         programStats.score = programStats.got_only_move = 0;
14947         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14948
14949         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14950         strcat(bookMove, bookHit);
14951         HandleMachineMove(bookMove, &first);
14952     }
14953 }
14954
14955
14956 void
14957 DisplayTwoMachinesTitle ()
14958 {
14959     char buf[MSG_SIZ];
14960     if (appData.matchGames > 0) {
14961         if(appData.tourneyFile[0]) {
14962           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14963                    gameInfo.white, _("vs."), gameInfo.black,
14964                    nextGame+1, appData.matchGames+1,
14965                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14966         } else
14967         if (first.twoMachinesColor[0] == 'w') {
14968           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14969                    gameInfo.white, _("vs."),  gameInfo.black,
14970                    first.matchWins, second.matchWins,
14971                    matchGame - 1 - (first.matchWins + second.matchWins));
14972         } else {
14973           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14974                    gameInfo.white, _("vs."), gameInfo.black,
14975                    second.matchWins, first.matchWins,
14976                    matchGame - 1 - (first.matchWins + second.matchWins));
14977         }
14978     } else {
14979       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14980     }
14981     DisplayTitle(buf);
14982 }
14983
14984 void
14985 SettingsMenuIfReady ()
14986 {
14987   if (second.lastPing != second.lastPong) {
14988     DisplayMessage("", _("Waiting for second chess program"));
14989     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14990     return;
14991   }
14992   ThawUI();
14993   DisplayMessage("", "");
14994   SettingsPopUp(&second);
14995 }
14996
14997 int
14998 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14999 {
15000     char buf[MSG_SIZ];
15001     if (cps->pr == NoProc) {
15002         StartChessProgram(cps);
15003         if (cps->protocolVersion == 1) {
15004           retry();
15005           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15006         } else {
15007           /* kludge: allow timeout for initial "feature" command */
15008           if(retry != TwoMachinesEventIfReady) FreezeUI();
15009           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15010           DisplayMessage("", buf);
15011           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15012         }
15013         return 1;
15014     }
15015     return 0;
15016 }
15017
15018 void
15019 TwoMachinesEvent P((void))
15020 {
15021     int i;
15022     char buf[MSG_SIZ];
15023     ChessProgramState *onmove;
15024     char *bookHit = NULL;
15025     static int stalling = 0;
15026     TimeMark now;
15027     long wait;
15028
15029     if (appData.noChessProgram) return;
15030
15031     switch (gameMode) {
15032       case TwoMachinesPlay:
15033         return;
15034       case MachinePlaysWhite:
15035       case MachinePlaysBlack:
15036         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15037             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15038             return;
15039         }
15040         /* fall through */
15041       case BeginningOfGame:
15042       case PlayFromGameFile:
15043       case EndOfGame:
15044         EditGameEvent();
15045         if (gameMode != EditGame) return;
15046         break;
15047       case EditPosition:
15048         EditPositionDone(TRUE);
15049         break;
15050       case AnalyzeMode:
15051       case AnalyzeFile:
15052         ExitAnalyzeMode();
15053         break;
15054       case EditGame:
15055       default:
15056         break;
15057     }
15058
15059 //    forwardMostMove = currentMove;
15060     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15061     startingEngine = TRUE;
15062
15063     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15064
15065     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15066     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15067       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15068       return;
15069     }
15070   if(!appData.epd) {
15071     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15072
15073     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15074                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15075         startingEngine = matchMode = FALSE;
15076         DisplayError("second engine does not play this", 0);
15077         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15078         EditGameEvent(); // switch back to EditGame mode
15079         return;
15080     }
15081
15082     if(!stalling) {
15083       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15084       SendToProgram("force\n", &second);
15085       stalling = 1;
15086       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15087       return;
15088     }
15089   }
15090     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15091     if(appData.matchPause>10000 || appData.matchPause<10)
15092                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15093     wait = SubtractTimeMarks(&now, &pauseStart);
15094     if(wait < appData.matchPause) {
15095         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15096         return;
15097     }
15098     // we are now committed to starting the game
15099     stalling = 0;
15100     DisplayMessage("", "");
15101   if(!appData.epd) {
15102     if (startedFromSetupPosition) {
15103         SendBoard(&second, backwardMostMove);
15104     if (appData.debugMode) {
15105         fprintf(debugFP, "Two Machines\n");
15106     }
15107     }
15108     for (i = backwardMostMove; i < forwardMostMove; i++) {
15109         SendMoveToProgram(i, &second);
15110     }
15111   }
15112
15113     gameMode = TwoMachinesPlay;
15114     pausing = startingEngine = FALSE;
15115     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15116     SetGameInfo();
15117     DisplayTwoMachinesTitle();
15118     firstMove = TRUE;
15119     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15120         onmove = &first;
15121     } else {
15122         onmove = &second;
15123     }
15124     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15125     SendToProgram(first.computerString, &first);
15126     if (first.sendName) {
15127       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15128       SendToProgram(buf, &first);
15129     }
15130   if(!appData.epd) {
15131     SendToProgram(second.computerString, &second);
15132     if (second.sendName) {
15133       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15134       SendToProgram(buf, &second);
15135     }
15136   }
15137
15138     ResetClocks();
15139     if (!first.sendTime || !second.sendTime) {
15140         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15141         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15142     }
15143     if (onmove->sendTime) {
15144       if (onmove->useColors) {
15145         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15146       }
15147       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15148     }
15149     if (onmove->useColors) {
15150       SendToProgram(onmove->twoMachinesColor, onmove);
15151     }
15152     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15153 //    SendToProgram("go\n", onmove);
15154     onmove->maybeThinking = TRUE;
15155     SetMachineThinkingEnables();
15156
15157     StartClocks();
15158
15159     if(bookHit) { // [HGM] book: simulate book reply
15160         static char bookMove[MSG_SIZ]; // a bit generous?
15161
15162         programStats.nodes = programStats.depth = programStats.time =
15163         programStats.score = programStats.got_only_move = 0;
15164         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15165
15166         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15167         strcat(bookMove, bookHit);
15168         savedMessage = bookMove; // args for deferred call
15169         savedState = onmove;
15170         ScheduleDelayedEvent(DeferredBookMove, 1);
15171     }
15172 }
15173
15174 void
15175 TrainingEvent ()
15176 {
15177     if (gameMode == Training) {
15178       SetTrainingModeOff();
15179       gameMode = PlayFromGameFile;
15180       DisplayMessage("", _("Training mode off"));
15181     } else {
15182       gameMode = Training;
15183       animateTraining = appData.animate;
15184
15185       /* make sure we are not already at the end of the game */
15186       if (currentMove < forwardMostMove) {
15187         SetTrainingModeOn();
15188         DisplayMessage("", _("Training mode on"));
15189       } else {
15190         gameMode = PlayFromGameFile;
15191         DisplayError(_("Already at end of game"), 0);
15192       }
15193     }
15194     ModeHighlight();
15195 }
15196
15197 void
15198 IcsClientEvent ()
15199 {
15200     if (!appData.icsActive) return;
15201     switch (gameMode) {
15202       case IcsPlayingWhite:
15203       case IcsPlayingBlack:
15204       case IcsObserving:
15205       case IcsIdle:
15206       case BeginningOfGame:
15207       case IcsExamining:
15208         return;
15209
15210       case EditGame:
15211         break;
15212
15213       case EditPosition:
15214         EditPositionDone(TRUE);
15215         break;
15216
15217       case AnalyzeMode:
15218       case AnalyzeFile:
15219         ExitAnalyzeMode();
15220         break;
15221
15222       default:
15223         EditGameEvent();
15224         break;
15225     }
15226
15227     gameMode = IcsIdle;
15228     ModeHighlight();
15229     return;
15230 }
15231
15232 void
15233 EditGameEvent ()
15234 {
15235     int i;
15236
15237     switch (gameMode) {
15238       case Training:
15239         SetTrainingModeOff();
15240         break;
15241       case MachinePlaysWhite:
15242       case MachinePlaysBlack:
15243       case BeginningOfGame:
15244         SendToProgram("force\n", &first);
15245         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15246             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15247                 char buf[MSG_SIZ];
15248                 abortEngineThink = TRUE;
15249                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15250                 SendToProgram(buf, &first);
15251                 DisplayMessage("Aborting engine think", "");
15252                 FreezeUI();
15253             }
15254         }
15255         SetUserThinkingEnables();
15256         break;
15257       case PlayFromGameFile:
15258         (void) StopLoadGameTimer();
15259         if (gameFileFP != NULL) {
15260             gameFileFP = NULL;
15261         }
15262         break;
15263       case EditPosition:
15264         EditPositionDone(TRUE);
15265         break;
15266       case AnalyzeMode:
15267       case AnalyzeFile:
15268         ExitAnalyzeMode();
15269         SendToProgram("force\n", &first);
15270         break;
15271       case TwoMachinesPlay:
15272         GameEnds(EndOfFile, NULL, GE_PLAYER);
15273         ResurrectChessProgram();
15274         SetUserThinkingEnables();
15275         break;
15276       case EndOfGame:
15277         ResurrectChessProgram();
15278         break;
15279       case IcsPlayingBlack:
15280       case IcsPlayingWhite:
15281         DisplayError(_("Warning: You are still playing a game"), 0);
15282         break;
15283       case IcsObserving:
15284         DisplayError(_("Warning: You are still observing a game"), 0);
15285         break;
15286       case IcsExamining:
15287         DisplayError(_("Warning: You are still examining a game"), 0);
15288         break;
15289       case IcsIdle:
15290         break;
15291       case EditGame:
15292       default:
15293         return;
15294     }
15295
15296     pausing = FALSE;
15297     StopClocks();
15298     first.offeredDraw = second.offeredDraw = 0;
15299
15300     if (gameMode == PlayFromGameFile) {
15301         whiteTimeRemaining = timeRemaining[0][currentMove];
15302         blackTimeRemaining = timeRemaining[1][currentMove];
15303         DisplayTitle("");
15304     }
15305
15306     if (gameMode == MachinePlaysWhite ||
15307         gameMode == MachinePlaysBlack ||
15308         gameMode == TwoMachinesPlay ||
15309         gameMode == EndOfGame) {
15310         i = forwardMostMove;
15311         while (i > currentMove) {
15312             SendToProgram("undo\n", &first);
15313             i--;
15314         }
15315         if(!adjustedClock) {
15316         whiteTimeRemaining = timeRemaining[0][currentMove];
15317         blackTimeRemaining = timeRemaining[1][currentMove];
15318         DisplayBothClocks();
15319         }
15320         if (whiteFlag || blackFlag) {
15321             whiteFlag = blackFlag = 0;
15322         }
15323         DisplayTitle("");
15324     }
15325
15326     gameMode = EditGame;
15327     ModeHighlight();
15328     SetGameInfo();
15329 }
15330
15331 void
15332 EditPositionEvent ()
15333 {
15334     int i;
15335     if (gameMode == EditPosition) {
15336         EditGameEvent();
15337         return;
15338     }
15339
15340     EditGameEvent();
15341     if (gameMode != EditGame) return;
15342
15343     gameMode = EditPosition;
15344     ModeHighlight();
15345     SetGameInfo();
15346     CopyBoard(rightsBoard, nullBoard);
15347     if (currentMove > 0)
15348       CopyBoard(boards[0], boards[currentMove]);
15349     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15350       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15351
15352     blackPlaysFirst = !WhiteOnMove(currentMove);
15353     ResetClocks();
15354     currentMove = forwardMostMove = backwardMostMove = 0;
15355     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15356     DisplayMove(-1);
15357     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15358 }
15359
15360 void
15361 ExitAnalyzeMode ()
15362 {
15363     /* [DM] icsEngineAnalyze - possible call from other functions */
15364     if (appData.icsEngineAnalyze) {
15365         appData.icsEngineAnalyze = FALSE;
15366
15367         DisplayMessage("",_("Close ICS engine analyze..."));
15368     }
15369     if (first.analysisSupport && first.analyzing) {
15370       SendToBoth("exit\n");
15371       first.analyzing = second.analyzing = FALSE;
15372     }
15373     thinkOutput[0] = NULLCHAR;
15374 }
15375
15376 void
15377 EditPositionDone (Boolean fakeRights)
15378 {
15379     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15380
15381     startedFromSetupPosition = TRUE;
15382     InitChessProgram(&first, FALSE);
15383     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15384       int r, f;
15385       boards[0][EP_STATUS] = EP_NONE;
15386       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15387       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15388         if(rightsBoard[r][f]) {
15389           ChessSquare p = boards[0][r][f];
15390           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15391           else if(p == king) boards[0][CASTLING][2] = f;
15392           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15393           else rightsBoard[r][f] = 2; // mark for second pass
15394         }
15395       }
15396       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15397         if(rightsBoard[r][f] == 2) {
15398           ChessSquare p = boards[0][r][f];
15399           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15400           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15401         }
15402       }
15403     }
15404     SendToProgram("force\n", &first);
15405     if (blackPlaysFirst) {
15406         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15407         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15408         currentMove = forwardMostMove = backwardMostMove = 1;
15409         CopyBoard(boards[1], boards[0]);
15410     } else {
15411         currentMove = forwardMostMove = backwardMostMove = 0;
15412     }
15413     SendBoard(&first, forwardMostMove);
15414     if (appData.debugMode) {
15415         fprintf(debugFP, "EditPosDone\n");
15416     }
15417     DisplayTitle("");
15418     DisplayMessage("", "");
15419     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15420     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15421     gameMode = EditGame;
15422     ModeHighlight();
15423     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15424     ClearHighlights(); /* [AS] */
15425 }
15426
15427 /* Pause for `ms' milliseconds */
15428 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15429 void
15430 TimeDelay (long ms)
15431 {
15432     TimeMark m1, m2;
15433
15434     GetTimeMark(&m1);
15435     do {
15436         GetTimeMark(&m2);
15437     } while (SubtractTimeMarks(&m2, &m1) < ms);
15438 }
15439
15440 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15441 void
15442 SendMultiLineToICS (char *buf)
15443 {
15444     char temp[MSG_SIZ+1], *p;
15445     int len;
15446
15447     len = strlen(buf);
15448     if (len > MSG_SIZ)
15449       len = MSG_SIZ;
15450
15451     strncpy(temp, buf, len);
15452     temp[len] = 0;
15453
15454     p = temp;
15455     while (*p) {
15456         if (*p == '\n' || *p == '\r')
15457           *p = ' ';
15458         ++p;
15459     }
15460
15461     strcat(temp, "\n");
15462     SendToICS(temp);
15463     SendToPlayer(temp, strlen(temp));
15464 }
15465
15466 void
15467 SetWhiteToPlayEvent ()
15468 {
15469     if (gameMode == EditPosition) {
15470         blackPlaysFirst = FALSE;
15471         DisplayBothClocks();    /* works because currentMove is 0 */
15472     } else if (gameMode == IcsExamining) {
15473         SendToICS(ics_prefix);
15474         SendToICS("tomove white\n");
15475     }
15476 }
15477
15478 void
15479 SetBlackToPlayEvent ()
15480 {
15481     if (gameMode == EditPosition) {
15482         blackPlaysFirst = TRUE;
15483         currentMove = 1;        /* kludge */
15484         DisplayBothClocks();
15485         currentMove = 0;
15486     } else if (gameMode == IcsExamining) {
15487         SendToICS(ics_prefix);
15488         SendToICS("tomove black\n");
15489     }
15490 }
15491
15492 void
15493 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15494 {
15495     char buf[MSG_SIZ];
15496     ChessSquare piece = boards[0][y][x];
15497     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15498     static int lastVariant;
15499     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15500
15501     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15502
15503     switch (selection) {
15504       case ClearBoard:
15505         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15506         MarkTargetSquares(1);
15507         CopyBoard(currentBoard, boards[0]);
15508         CopyBoard(menuBoard, initialPosition);
15509         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15510             SendToICS(ics_prefix);
15511             SendToICS("bsetup clear\n");
15512         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15513             SendToICS(ics_prefix);
15514             SendToICS("clearboard\n");
15515         } else {
15516             int nonEmpty = 0;
15517             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15518                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15519                 for (y = 0; y < BOARD_HEIGHT; y++) {
15520                     if (gameMode == IcsExamining) {
15521                         if (boards[currentMove][y][x] != EmptySquare) {
15522                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15523                                     AAA + x, ONE + y);
15524                             SendToICS(buf);
15525                         }
15526                     } else if(boards[0][y][x] != DarkSquare) {
15527                         if(boards[0][y][x] != p) nonEmpty++;
15528                         boards[0][y][x] = p;
15529                     }
15530                 }
15531             }
15532             CopyBoard(rightsBoard, nullBoard);
15533             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15534                 int r, i;
15535                 for(r = 0; r < BOARD_HEIGHT; r++) {
15536                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15537                     ChessSquare p = menuBoard[r][x];
15538                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15539                   }
15540                 }
15541                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15542                 DisplayMessage("Clicking clock again restores position", "");
15543                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15544                 if(!nonEmpty) { // asked to clear an empty board
15545                     CopyBoard(boards[0], menuBoard);
15546                 } else
15547                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15548                     CopyBoard(boards[0], initialPosition);
15549                 } else
15550                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15551                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15552                     CopyBoard(boards[0], erasedBoard);
15553                 } else
15554                     CopyBoard(erasedBoard, currentBoard);
15555
15556                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15557                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15558             }
15559         }
15560         if (gameMode == EditPosition) {
15561             DrawPosition(FALSE, boards[0]);
15562         }
15563         break;
15564
15565       case WhitePlay:
15566         SetWhiteToPlayEvent();
15567         break;
15568
15569       case BlackPlay:
15570         SetBlackToPlayEvent();
15571         break;
15572
15573       case EmptySquare:
15574         if (gameMode == IcsExamining) {
15575             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15576             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15577             SendToICS(buf);
15578         } else {
15579             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15580                 if(x == BOARD_LEFT-2) {
15581                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15582                     boards[0][y][1] = 0;
15583                 } else
15584                 if(x == BOARD_RGHT+1) {
15585                     if(y >= gameInfo.holdingsSize) break;
15586                     boards[0][y][BOARD_WIDTH-2] = 0;
15587                 } else break;
15588             }
15589             boards[0][y][x] = EmptySquare;
15590             DrawPosition(FALSE, boards[0]);
15591         }
15592         break;
15593
15594       case PromotePiece:
15595         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15596            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15597             selection = (ChessSquare) (PROMOTED(piece));
15598         } else if(piece == EmptySquare) selection = WhiteSilver;
15599         else selection = (ChessSquare)((int)piece - 1);
15600         goto defaultlabel;
15601
15602       case DemotePiece:
15603         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15604            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15605             selection = (ChessSquare) (DEMOTED(piece));
15606         } else if(piece == EmptySquare) selection = BlackSilver;
15607         else selection = (ChessSquare)((int)piece + 1);
15608         goto defaultlabel;
15609
15610       case WhiteQueen:
15611       case BlackQueen:
15612         if(gameInfo.variant == VariantShatranj ||
15613            gameInfo.variant == VariantXiangqi  ||
15614            gameInfo.variant == VariantCourier  ||
15615            gameInfo.variant == VariantASEAN    ||
15616            gameInfo.variant == VariantMakruk     )
15617             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15618         goto defaultlabel;
15619
15620       case WhiteRook:
15621         baseRank = 0;
15622       case BlackRook:
15623         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15624         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15625         goto defaultlabel;
15626
15627       case WhiteKing:
15628         baseRank = 0;
15629       case BlackKing:
15630         if(gameInfo.variant == VariantXiangqi)
15631             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15632         if(gameInfo.variant == VariantKnightmate)
15633             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15634         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15635       default:
15636         defaultlabel:
15637         if (gameMode == IcsExamining) {
15638             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15639             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15640                      PieceToChar(selection), AAA + x, ONE + y);
15641             SendToICS(buf);
15642         } else {
15643             rightsBoard[y][x] = hasRights;
15644             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15645                 int n;
15646                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15647                     n = PieceToNumber(selection - BlackPawn);
15648                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15649                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15650                     boards[0][BOARD_HEIGHT-1-n][1]++;
15651                 } else
15652                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15653                     n = PieceToNumber(selection);
15654                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15655                     boards[0][n][BOARD_WIDTH-1] = selection;
15656                     boards[0][n][BOARD_WIDTH-2]++;
15657                 }
15658             } else
15659             boards[0][y][x] = selection;
15660             DrawPosition(TRUE, boards[0]);
15661             ClearHighlights();
15662             fromX = fromY = -1;
15663         }
15664         break;
15665     }
15666 }
15667
15668
15669 void
15670 DropMenuEvent (ChessSquare selection, int x, int y)
15671 {
15672     ChessMove moveType;
15673
15674     switch (gameMode) {
15675       case IcsPlayingWhite:
15676       case MachinePlaysBlack:
15677         if (!WhiteOnMove(currentMove)) {
15678             DisplayMoveError(_("It is Black's turn"));
15679             return;
15680         }
15681         moveType = WhiteDrop;
15682         break;
15683       case IcsPlayingBlack:
15684       case MachinePlaysWhite:
15685         if (WhiteOnMove(currentMove)) {
15686             DisplayMoveError(_("It is White's turn"));
15687             return;
15688         }
15689         moveType = BlackDrop;
15690         break;
15691       case EditGame:
15692         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15693         break;
15694       default:
15695         return;
15696     }
15697
15698     if (moveType == BlackDrop && selection < BlackPawn) {
15699       selection = (ChessSquare) ((int) selection
15700                                  + (int) BlackPawn - (int) WhitePawn);
15701     }
15702     if (boards[currentMove][y][x] != EmptySquare) {
15703         DisplayMoveError(_("That square is occupied"));
15704         return;
15705     }
15706
15707     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15708 }
15709
15710 void
15711 AcceptEvent ()
15712 {
15713     /* Accept a pending offer of any kind from opponent */
15714
15715     if (appData.icsActive) {
15716         SendToICS(ics_prefix);
15717         SendToICS("accept\n");
15718     } else if (cmailMsgLoaded) {
15719         if (currentMove == cmailOldMove &&
15720             commentList[cmailOldMove] != NULL &&
15721             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15722                    "Black offers a draw" : "White offers a draw")) {
15723             TruncateGame();
15724             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15725             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15726         } else {
15727             DisplayError(_("There is no pending offer on this move"), 0);
15728             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15729         }
15730     } else {
15731         /* Not used for offers from chess program */
15732     }
15733 }
15734
15735 void
15736 DeclineEvent ()
15737 {
15738     /* Decline a pending offer of any kind from opponent */
15739
15740     if (appData.icsActive) {
15741         SendToICS(ics_prefix);
15742         SendToICS("decline\n");
15743     } else if (cmailMsgLoaded) {
15744         if (currentMove == cmailOldMove &&
15745             commentList[cmailOldMove] != NULL &&
15746             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15747                    "Black offers a draw" : "White offers a draw")) {
15748 #ifdef NOTDEF
15749             AppendComment(cmailOldMove, "Draw declined", TRUE);
15750             DisplayComment(cmailOldMove - 1, "Draw declined");
15751 #endif /*NOTDEF*/
15752         } else {
15753             DisplayError(_("There is no pending offer on this move"), 0);
15754         }
15755     } else {
15756         /* Not used for offers from chess program */
15757     }
15758 }
15759
15760 void
15761 RematchEvent ()
15762 {
15763     /* Issue ICS rematch command */
15764     if (appData.icsActive) {
15765         SendToICS(ics_prefix);
15766         SendToICS("rematch\n");
15767     }
15768 }
15769
15770 void
15771 CallFlagEvent ()
15772 {
15773     /* Call your opponent's flag (claim a win on time) */
15774     if (appData.icsActive) {
15775         SendToICS(ics_prefix);
15776         SendToICS("flag\n");
15777     } else {
15778         switch (gameMode) {
15779           default:
15780             return;
15781           case MachinePlaysWhite:
15782             if (whiteFlag) {
15783                 if (blackFlag)
15784                   GameEnds(GameIsDrawn, "Both players ran out of time",
15785                            GE_PLAYER);
15786                 else
15787                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15788             } else {
15789                 DisplayError(_("Your opponent is not out of time"), 0);
15790             }
15791             break;
15792           case MachinePlaysBlack:
15793             if (blackFlag) {
15794                 if (whiteFlag)
15795                   GameEnds(GameIsDrawn, "Both players ran out of time",
15796                            GE_PLAYER);
15797                 else
15798                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15799             } else {
15800                 DisplayError(_("Your opponent is not out of time"), 0);
15801             }
15802             break;
15803         }
15804     }
15805 }
15806
15807 void
15808 ClockClick (int which)
15809 {       // [HGM] code moved to back-end from winboard.c
15810         if(which) { // black clock
15811           if (gameMode == EditPosition || gameMode == IcsExamining) {
15812             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15813             SetBlackToPlayEvent();
15814           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15815                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15816           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15817           } else if (shiftKey) {
15818             AdjustClock(which, -1);
15819           } else if (gameMode == IcsPlayingWhite ||
15820                      gameMode == MachinePlaysBlack) {
15821             CallFlagEvent();
15822           }
15823         } else { // white clock
15824           if (gameMode == EditPosition || gameMode == IcsExamining) {
15825             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15826             SetWhiteToPlayEvent();
15827           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15828                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15829           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15830           } else if (shiftKey) {
15831             AdjustClock(which, -1);
15832           } else if (gameMode == IcsPlayingBlack ||
15833                    gameMode == MachinePlaysWhite) {
15834             CallFlagEvent();
15835           }
15836         }
15837 }
15838
15839 void
15840 DrawEvent ()
15841 {
15842     /* Offer draw or accept pending draw offer from opponent */
15843
15844     if (appData.icsActive) {
15845         /* Note: tournament rules require draw offers to be
15846            made after you make your move but before you punch
15847            your clock.  Currently ICS doesn't let you do that;
15848            instead, you immediately punch your clock after making
15849            a move, but you can offer a draw at any time. */
15850
15851         SendToICS(ics_prefix);
15852         SendToICS("draw\n");
15853         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15854     } else if (cmailMsgLoaded) {
15855         if (currentMove == cmailOldMove &&
15856             commentList[cmailOldMove] != NULL &&
15857             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15858                    "Black offers a draw" : "White offers a draw")) {
15859             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15860             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15861         } else if (currentMove == cmailOldMove + 1) {
15862             char *offer = WhiteOnMove(cmailOldMove) ?
15863               "White offers a draw" : "Black offers a draw";
15864             AppendComment(currentMove, offer, TRUE);
15865             DisplayComment(currentMove - 1, offer);
15866             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15867         } else {
15868             DisplayError(_("You must make your move before offering a draw"), 0);
15869             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15870         }
15871     } else if (first.offeredDraw) {
15872         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15873     } else {
15874         if (first.sendDrawOffers) {
15875             SendToProgram("draw\n", &first);
15876             userOfferedDraw = TRUE;
15877         }
15878     }
15879 }
15880
15881 void
15882 AdjournEvent ()
15883 {
15884     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15885
15886     if (appData.icsActive) {
15887         SendToICS(ics_prefix);
15888         SendToICS("adjourn\n");
15889     } else {
15890         /* Currently GNU Chess doesn't offer or accept Adjourns */
15891     }
15892 }
15893
15894
15895 void
15896 AbortEvent ()
15897 {
15898     /* Offer Abort or accept pending Abort offer from opponent */
15899
15900     if (appData.icsActive) {
15901         SendToICS(ics_prefix);
15902         SendToICS("abort\n");
15903     } else {
15904         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15905     }
15906 }
15907
15908 void
15909 ResignEvent ()
15910 {
15911     /* Resign.  You can do this even if it's not your turn. */
15912
15913     if (appData.icsActive) {
15914         SendToICS(ics_prefix);
15915         SendToICS("resign\n");
15916     } else {
15917         switch (gameMode) {
15918           case MachinePlaysWhite:
15919             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15920             break;
15921           case MachinePlaysBlack:
15922             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15923             break;
15924           case EditGame:
15925             if (cmailMsgLoaded) {
15926                 TruncateGame();
15927                 if (WhiteOnMove(cmailOldMove)) {
15928                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15929                 } else {
15930                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15931                 }
15932                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15933             }
15934             break;
15935           default:
15936             break;
15937         }
15938     }
15939 }
15940
15941
15942 void
15943 StopObservingEvent ()
15944 {
15945     /* Stop observing current games */
15946     SendToICS(ics_prefix);
15947     SendToICS("unobserve\n");
15948 }
15949
15950 void
15951 StopExaminingEvent ()
15952 {
15953     /* Stop observing current game */
15954     SendToICS(ics_prefix);
15955     SendToICS("unexamine\n");
15956 }
15957
15958 void
15959 ForwardInner (int target)
15960 {
15961     int limit; int oldSeekGraphUp = seekGraphUp;
15962
15963     if (appData.debugMode)
15964         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15965                 target, currentMove, forwardMostMove);
15966
15967     if (gameMode == EditPosition)
15968       return;
15969
15970     seekGraphUp = FALSE;
15971     MarkTargetSquares(1);
15972     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15973
15974     if (gameMode == PlayFromGameFile && !pausing)
15975       PauseEvent();
15976
15977     if (gameMode == IcsExamining && pausing)
15978       limit = pauseExamForwardMostMove;
15979     else
15980       limit = forwardMostMove;
15981
15982     if (target > limit) target = limit;
15983
15984     if (target > 0 && moveList[target - 1][0]) {
15985         int fromX, fromY, toX, toY;
15986         toX = moveList[target - 1][2] - AAA;
15987         toY = moveList[target - 1][3] - ONE;
15988         if (moveList[target - 1][1] == '@') {
15989             if (appData.highlightLastMove) {
15990                 SetHighlights(-1, -1, toX, toY);
15991             }
15992         } else {
15993             fromX = moveList[target - 1][0] - AAA;
15994             fromY = moveList[target - 1][1] - ONE;
15995             if (target == currentMove + 1) {
15996                 if(moveList[target - 1][4] == ';') { // multi-leg
15997                     killX = moveList[target - 1][5] - AAA;
15998                     killY = moveList[target - 1][6] - ONE;
15999                 }
16000                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16001                 killX = killY = -1;
16002             }
16003             if (appData.highlightLastMove) {
16004                 SetHighlights(fromX, fromY, toX, toY);
16005             }
16006         }
16007     }
16008     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16009         gameMode == Training || gameMode == PlayFromGameFile ||
16010         gameMode == AnalyzeFile) {
16011         while (currentMove < target) {
16012             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16013             SendMoveToProgram(currentMove++, &first);
16014         }
16015     } else {
16016         currentMove = target;
16017     }
16018
16019     if (gameMode == EditGame || gameMode == EndOfGame) {
16020         whiteTimeRemaining = timeRemaining[0][currentMove];
16021         blackTimeRemaining = timeRemaining[1][currentMove];
16022     }
16023     DisplayBothClocks();
16024     DisplayMove(currentMove - 1);
16025     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16026     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16027     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16028         DisplayComment(currentMove - 1, commentList[currentMove]);
16029     }
16030     ClearMap(); // [HGM] exclude: invalidate map
16031 }
16032
16033
16034 void
16035 ForwardEvent ()
16036 {
16037     if (gameMode == IcsExamining && !pausing) {
16038         SendToICS(ics_prefix);
16039         SendToICS("forward\n");
16040     } else {
16041         ForwardInner(currentMove + 1);
16042     }
16043 }
16044
16045 void
16046 ToEndEvent ()
16047 {
16048     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16049         /* to optimze, we temporarily turn off analysis mode while we feed
16050          * the remaining moves to the engine. Otherwise we get analysis output
16051          * after each move.
16052          */
16053         if (first.analysisSupport) {
16054           SendToProgram("exit\nforce\n", &first);
16055           first.analyzing = FALSE;
16056         }
16057     }
16058
16059     if (gameMode == IcsExamining && !pausing) {
16060         SendToICS(ics_prefix);
16061         SendToICS("forward 999999\n");
16062     } else {
16063         ForwardInner(forwardMostMove);
16064     }
16065
16066     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16067         /* we have fed all the moves, so reactivate analysis mode */
16068         SendToProgram("analyze\n", &first);
16069         first.analyzing = TRUE;
16070         /*first.maybeThinking = TRUE;*/
16071         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16072     }
16073 }
16074
16075 void
16076 BackwardInner (int target)
16077 {
16078     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16079
16080     if (appData.debugMode)
16081         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16082                 target, currentMove, forwardMostMove);
16083
16084     if (gameMode == EditPosition) return;
16085     seekGraphUp = FALSE;
16086     MarkTargetSquares(1);
16087     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16088     if (currentMove <= backwardMostMove) {
16089         ClearHighlights();
16090         DrawPosition(full_redraw, boards[currentMove]);
16091         return;
16092     }
16093     if (gameMode == PlayFromGameFile && !pausing)
16094       PauseEvent();
16095
16096     if (moveList[target][0]) {
16097         int fromX, fromY, toX, toY;
16098         toX = moveList[target][2] - AAA;
16099         toY = moveList[target][3] - ONE;
16100         if (moveList[target][1] == '@') {
16101             if (appData.highlightLastMove) {
16102                 SetHighlights(-1, -1, toX, toY);
16103             }
16104         } else {
16105             fromX = moveList[target][0] - AAA;
16106             fromY = moveList[target][1] - ONE;
16107             if (target == currentMove - 1) {
16108                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16109             }
16110             if (appData.highlightLastMove) {
16111                 SetHighlights(fromX, fromY, toX, toY);
16112             }
16113         }
16114     }
16115     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16116         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16117         while (currentMove > target) {
16118             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16119                 // null move cannot be undone. Reload program with move history before it.
16120                 int i;
16121                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16122                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16123                 }
16124                 SendBoard(&first, i);
16125               if(second.analyzing) SendBoard(&second, i);
16126                 for(currentMove=i; currentMove<target; currentMove++) {
16127                     SendMoveToProgram(currentMove, &first);
16128                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16129                 }
16130                 break;
16131             }
16132             SendToBoth("undo\n");
16133             currentMove--;
16134         }
16135     } else {
16136         currentMove = target;
16137     }
16138
16139     if (gameMode == EditGame || gameMode == EndOfGame) {
16140         whiteTimeRemaining = timeRemaining[0][currentMove];
16141         blackTimeRemaining = timeRemaining[1][currentMove];
16142     }
16143     DisplayBothClocks();
16144     DisplayMove(currentMove - 1);
16145     DrawPosition(full_redraw, boards[currentMove]);
16146     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16147     // [HGM] PV info: routine tests if comment empty
16148     DisplayComment(currentMove - 1, commentList[currentMove]);
16149     ClearMap(); // [HGM] exclude: invalidate map
16150 }
16151
16152 void
16153 BackwardEvent ()
16154 {
16155     if (gameMode == IcsExamining && !pausing) {
16156         SendToICS(ics_prefix);
16157         SendToICS("backward\n");
16158     } else {
16159         BackwardInner(currentMove - 1);
16160     }
16161 }
16162
16163 void
16164 ToStartEvent ()
16165 {
16166     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16167         /* to optimize, we temporarily turn off analysis mode while we undo
16168          * all the moves. Otherwise we get analysis output after each undo.
16169          */
16170         if (first.analysisSupport) {
16171           SendToProgram("exit\nforce\n", &first);
16172           first.analyzing = FALSE;
16173         }
16174     }
16175
16176     if (gameMode == IcsExamining && !pausing) {
16177         SendToICS(ics_prefix);
16178         SendToICS("backward 999999\n");
16179     } else {
16180         BackwardInner(backwardMostMove);
16181     }
16182
16183     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16184         /* we have fed all the moves, so reactivate analysis mode */
16185         SendToProgram("analyze\n", &first);
16186         first.analyzing = TRUE;
16187         /*first.maybeThinking = TRUE;*/
16188         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16189     }
16190 }
16191
16192 void
16193 ToNrEvent (int to)
16194 {
16195   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16196   if (to >= forwardMostMove) to = forwardMostMove;
16197   if (to <= backwardMostMove) to = backwardMostMove;
16198   if (to < currentMove) {
16199     BackwardInner(to);
16200   } else {
16201     ForwardInner(to);
16202   }
16203 }
16204
16205 void
16206 RevertEvent (Boolean annotate)
16207 {
16208     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16209         return;
16210     }
16211     if (gameMode != IcsExamining) {
16212         DisplayError(_("You are not examining a game"), 0);
16213         return;
16214     }
16215     if (pausing) {
16216         DisplayError(_("You can't revert while pausing"), 0);
16217         return;
16218     }
16219     SendToICS(ics_prefix);
16220     SendToICS("revert\n");
16221 }
16222
16223 void
16224 RetractMoveEvent ()
16225 {
16226     switch (gameMode) {
16227       case MachinePlaysWhite:
16228       case MachinePlaysBlack:
16229         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16230             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16231             return;
16232         }
16233         if (forwardMostMove < 2) return;
16234         currentMove = forwardMostMove = forwardMostMove - 2;
16235         whiteTimeRemaining = timeRemaining[0][currentMove];
16236         blackTimeRemaining = timeRemaining[1][currentMove];
16237         DisplayBothClocks();
16238         DisplayMove(currentMove - 1);
16239         ClearHighlights();/*!! could figure this out*/
16240         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16241         SendToProgram("remove\n", &first);
16242         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16243         break;
16244
16245       case BeginningOfGame:
16246       default:
16247         break;
16248
16249       case IcsPlayingWhite:
16250       case IcsPlayingBlack:
16251         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16252             SendToICS(ics_prefix);
16253             SendToICS("takeback 2\n");
16254         } else {
16255             SendToICS(ics_prefix);
16256             SendToICS("takeback 1\n");
16257         }
16258         break;
16259     }
16260 }
16261
16262 void
16263 MoveNowEvent ()
16264 {
16265     ChessProgramState *cps;
16266
16267     switch (gameMode) {
16268       case MachinePlaysWhite:
16269         if (!WhiteOnMove(forwardMostMove)) {
16270             DisplayError(_("It is your turn"), 0);
16271             return;
16272         }
16273         cps = &first;
16274         break;
16275       case MachinePlaysBlack:
16276         if (WhiteOnMove(forwardMostMove)) {
16277             DisplayError(_("It is your turn"), 0);
16278             return;
16279         }
16280         cps = &first;
16281         break;
16282       case TwoMachinesPlay:
16283         if (WhiteOnMove(forwardMostMove) ==
16284             (first.twoMachinesColor[0] == 'w')) {
16285             cps = &first;
16286         } else {
16287             cps = &second;
16288         }
16289         break;
16290       case BeginningOfGame:
16291       default:
16292         return;
16293     }
16294     SendToProgram("?\n", cps);
16295 }
16296
16297 void
16298 TruncateGameEvent ()
16299 {
16300     EditGameEvent();
16301     if (gameMode != EditGame) return;
16302     TruncateGame();
16303 }
16304
16305 void
16306 TruncateGame ()
16307 {
16308     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16309     if (forwardMostMove > currentMove) {
16310         if (gameInfo.resultDetails != NULL) {
16311             free(gameInfo.resultDetails);
16312             gameInfo.resultDetails = NULL;
16313             gameInfo.result = GameUnfinished;
16314         }
16315         forwardMostMove = currentMove;
16316         HistorySet(parseList, backwardMostMove, forwardMostMove,
16317                    currentMove-1);
16318     }
16319 }
16320
16321 void
16322 HintEvent ()
16323 {
16324     if (appData.noChessProgram) return;
16325     switch (gameMode) {
16326       case MachinePlaysWhite:
16327         if (WhiteOnMove(forwardMostMove)) {
16328             DisplayError(_("Wait until your turn."), 0);
16329             return;
16330         }
16331         break;
16332       case BeginningOfGame:
16333       case MachinePlaysBlack:
16334         if (!WhiteOnMove(forwardMostMove)) {
16335             DisplayError(_("Wait until your turn."), 0);
16336             return;
16337         }
16338         break;
16339       default:
16340         DisplayError(_("No hint available"), 0);
16341         return;
16342     }
16343     SendToProgram("hint\n", &first);
16344     hintRequested = TRUE;
16345 }
16346
16347 int
16348 SaveSelected (FILE *g, int dummy, char *dummy2)
16349 {
16350     ListGame * lg = (ListGame *) gameList.head;
16351     int nItem, cnt=0;
16352     FILE *f;
16353
16354     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16355         DisplayError(_("Game list not loaded or empty"), 0);
16356         return 0;
16357     }
16358
16359     creatingBook = TRUE; // suppresses stuff during load game
16360
16361     /* Get list size */
16362     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16363         if(lg->position >= 0) { // selected?
16364             LoadGame(f, nItem, "", TRUE);
16365             SaveGamePGN2(g); // leaves g open
16366             cnt++; DoEvents();
16367         }
16368         lg = (ListGame *) lg->node.succ;
16369     }
16370
16371     fclose(g);
16372     creatingBook = FALSE;
16373
16374     return cnt;
16375 }
16376
16377 void
16378 CreateBookEvent ()
16379 {
16380     ListGame * lg = (ListGame *) gameList.head;
16381     FILE *f, *g;
16382     int nItem;
16383     static int secondTime = FALSE;
16384
16385     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16386         DisplayError(_("Game list not loaded or empty"), 0);
16387         return;
16388     }
16389
16390     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16391         fclose(g);
16392         secondTime++;
16393         DisplayNote(_("Book file exists! Try again for overwrite."));
16394         return;
16395     }
16396
16397     creatingBook = TRUE;
16398     secondTime = FALSE;
16399
16400     /* Get list size */
16401     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16402         if(lg->position >= 0) {
16403             LoadGame(f, nItem, "", TRUE);
16404             AddGameToBook(TRUE);
16405             DoEvents();
16406         }
16407         lg = (ListGame *) lg->node.succ;
16408     }
16409
16410     creatingBook = FALSE;
16411     FlushBook();
16412 }
16413
16414 void
16415 BookEvent ()
16416 {
16417     if (appData.noChessProgram) return;
16418     switch (gameMode) {
16419       case MachinePlaysWhite:
16420         if (WhiteOnMove(forwardMostMove)) {
16421             DisplayError(_("Wait until your turn."), 0);
16422             return;
16423         }
16424         break;
16425       case BeginningOfGame:
16426       case MachinePlaysBlack:
16427         if (!WhiteOnMove(forwardMostMove)) {
16428             DisplayError(_("Wait until your turn."), 0);
16429             return;
16430         }
16431         break;
16432       case EditPosition:
16433         EditPositionDone(TRUE);
16434         break;
16435       case TwoMachinesPlay:
16436         return;
16437       default:
16438         break;
16439     }
16440     SendToProgram("bk\n", &first);
16441     bookOutput[0] = NULLCHAR;
16442     bookRequested = TRUE;
16443 }
16444
16445 void
16446 AboutGameEvent ()
16447 {
16448     char *tags = PGNTags(&gameInfo);
16449     TagsPopUp(tags, CmailMsg());
16450     free(tags);
16451 }
16452
16453 /* end button procedures */
16454
16455 void
16456 PrintPosition (FILE *fp, int move)
16457 {
16458     int i, j;
16459
16460     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16461         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16462             char c = PieceToChar(boards[move][i][j]);
16463             fputc(c == '?' ? '.' : c, fp);
16464             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16465         }
16466     }
16467     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16468       fprintf(fp, "white to play\n");
16469     else
16470       fprintf(fp, "black to play\n");
16471 }
16472
16473 void
16474 PrintOpponents (FILE *fp)
16475 {
16476     if (gameInfo.white != NULL) {
16477         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16478     } else {
16479         fprintf(fp, "\n");
16480     }
16481 }
16482
16483 /* Find last component of program's own name, using some heuristics */
16484 void
16485 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16486 {
16487     char *p, *q, c;
16488     int local = (strcmp(host, "localhost") == 0);
16489     while (!local && (p = strchr(prog, ';')) != NULL) {
16490         p++;
16491         while (*p == ' ') p++;
16492         prog = p;
16493     }
16494     if (*prog == '"' || *prog == '\'') {
16495         q = strchr(prog + 1, *prog);
16496     } else {
16497         q = strchr(prog, ' ');
16498     }
16499     if (q == NULL) q = prog + strlen(prog);
16500     p = q;
16501     while (p >= prog && *p != '/' && *p != '\\') p--;
16502     p++;
16503     if(p == prog && *p == '"') p++;
16504     c = *q; *q = 0;
16505     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16506     memcpy(buf, p, q - p);
16507     buf[q - p] = NULLCHAR;
16508     if (!local) {
16509         strcat(buf, "@");
16510         strcat(buf, host);
16511     }
16512 }
16513
16514 char *
16515 TimeControlTagValue ()
16516 {
16517     char buf[MSG_SIZ];
16518     if (!appData.clockMode) {
16519       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16520     } else if (movesPerSession > 0) {
16521       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16522     } else if (timeIncrement == 0) {
16523       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16524     } else {
16525       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16526     }
16527     return StrSave(buf);
16528 }
16529
16530 void
16531 SetGameInfo ()
16532 {
16533     /* This routine is used only for certain modes */
16534     VariantClass v = gameInfo.variant;
16535     ChessMove r = GameUnfinished;
16536     char *p = NULL;
16537
16538     if(keepInfo) return;
16539
16540     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16541         r = gameInfo.result;
16542         p = gameInfo.resultDetails;
16543         gameInfo.resultDetails = NULL;
16544     }
16545     ClearGameInfo(&gameInfo);
16546     gameInfo.variant = v;
16547
16548     switch (gameMode) {
16549       case MachinePlaysWhite:
16550         gameInfo.event = StrSave( appData.pgnEventHeader );
16551         gameInfo.site = StrSave(HostName());
16552         gameInfo.date = PGNDate();
16553         gameInfo.round = StrSave("-");
16554         gameInfo.white = StrSave(first.tidy);
16555         gameInfo.black = StrSave(UserName());
16556         gameInfo.timeControl = TimeControlTagValue();
16557         break;
16558
16559       case MachinePlaysBlack:
16560         gameInfo.event = StrSave( appData.pgnEventHeader );
16561         gameInfo.site = StrSave(HostName());
16562         gameInfo.date = PGNDate();
16563         gameInfo.round = StrSave("-");
16564         gameInfo.white = StrSave(UserName());
16565         gameInfo.black = StrSave(first.tidy);
16566         gameInfo.timeControl = TimeControlTagValue();
16567         break;
16568
16569       case TwoMachinesPlay:
16570         gameInfo.event = StrSave( appData.pgnEventHeader );
16571         gameInfo.site = StrSave(HostName());
16572         gameInfo.date = PGNDate();
16573         if (roundNr > 0) {
16574             char buf[MSG_SIZ];
16575             snprintf(buf, MSG_SIZ, "%d", roundNr);
16576             gameInfo.round = StrSave(buf);
16577         } else {
16578             gameInfo.round = StrSave("-");
16579         }
16580         if (first.twoMachinesColor[0] == 'w') {
16581             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16582             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16583         } else {
16584             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16585             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16586         }
16587         gameInfo.timeControl = TimeControlTagValue();
16588         break;
16589
16590       case EditGame:
16591         gameInfo.event = StrSave("Edited game");
16592         gameInfo.site = StrSave(HostName());
16593         gameInfo.date = PGNDate();
16594         gameInfo.round = StrSave("-");
16595         gameInfo.white = StrSave("-");
16596         gameInfo.black = StrSave("-");
16597         gameInfo.result = r;
16598         gameInfo.resultDetails = p;
16599         break;
16600
16601       case EditPosition:
16602         gameInfo.event = StrSave("Edited position");
16603         gameInfo.site = StrSave(HostName());
16604         gameInfo.date = PGNDate();
16605         gameInfo.round = StrSave("-");
16606         gameInfo.white = StrSave("-");
16607         gameInfo.black = StrSave("-");
16608         break;
16609
16610       case IcsPlayingWhite:
16611       case IcsPlayingBlack:
16612       case IcsObserving:
16613       case IcsExamining:
16614         break;
16615
16616       case PlayFromGameFile:
16617         gameInfo.event = StrSave("Game from non-PGN file");
16618         gameInfo.site = StrSave(HostName());
16619         gameInfo.date = PGNDate();
16620         gameInfo.round = StrSave("-");
16621         gameInfo.white = StrSave("?");
16622         gameInfo.black = StrSave("?");
16623         break;
16624
16625       default:
16626         break;
16627     }
16628 }
16629
16630 void
16631 ReplaceComment (int index, char *text)
16632 {
16633     int len;
16634     char *p;
16635     float score;
16636
16637     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16638        pvInfoList[index-1].depth == len &&
16639        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16640        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16641     while (*text == '\n') text++;
16642     len = strlen(text);
16643     while (len > 0 && text[len - 1] == '\n') len--;
16644
16645     if (commentList[index] != NULL)
16646       free(commentList[index]);
16647
16648     if (len == 0) {
16649         commentList[index] = NULL;
16650         return;
16651     }
16652   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16653       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16654       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16655     commentList[index] = (char *) malloc(len + 2);
16656     strncpy(commentList[index], text, len);
16657     commentList[index][len] = '\n';
16658     commentList[index][len + 1] = NULLCHAR;
16659   } else {
16660     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16661     char *p;
16662     commentList[index] = (char *) malloc(len + 7);
16663     safeStrCpy(commentList[index], "{\n", 3);
16664     safeStrCpy(commentList[index]+2, text, len+1);
16665     commentList[index][len+2] = NULLCHAR;
16666     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16667     strcat(commentList[index], "\n}\n");
16668   }
16669 }
16670
16671 void
16672 CrushCRs (char *text)
16673 {
16674   char *p = text;
16675   char *q = text;
16676   char ch;
16677
16678   do {
16679     ch = *p++;
16680     if (ch == '\r') continue;
16681     *q++ = ch;
16682   } while (ch != '\0');
16683 }
16684
16685 void
16686 AppendComment (int index, char *text, Boolean addBraces)
16687 /* addBraces  tells if we should add {} */
16688 {
16689     int oldlen, len;
16690     char *old;
16691
16692 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16693     if(addBraces == 3) addBraces = 0; else // force appending literally
16694     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16695
16696     CrushCRs(text);
16697     while (*text == '\n') text++;
16698     len = strlen(text);
16699     while (len > 0 && text[len - 1] == '\n') len--;
16700     text[len] = NULLCHAR;
16701
16702     if (len == 0) return;
16703
16704     if (commentList[index] != NULL) {
16705       Boolean addClosingBrace = addBraces;
16706         old = commentList[index];
16707         oldlen = strlen(old);
16708         while(commentList[index][oldlen-1] ==  '\n')
16709           commentList[index][--oldlen] = NULLCHAR;
16710         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16711         safeStrCpy(commentList[index], old, oldlen + len + 6);
16712         free(old);
16713         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16714         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16715           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16716           while (*text == '\n') { text++; len--; }
16717           commentList[index][--oldlen] = NULLCHAR;
16718       }
16719         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16720         else          strcat(commentList[index], "\n");
16721         strcat(commentList[index], text);
16722         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16723         else          strcat(commentList[index], "\n");
16724     } else {
16725         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16726         if(addBraces)
16727           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16728         else commentList[index][0] = NULLCHAR;
16729         strcat(commentList[index], text);
16730         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16731         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16732     }
16733 }
16734
16735 static char *
16736 FindStr (char * text, char * sub_text)
16737 {
16738     char * result = strstr( text, sub_text );
16739
16740     if( result != NULL ) {
16741         result += strlen( sub_text );
16742     }
16743
16744     return result;
16745 }
16746
16747 /* [AS] Try to extract PV info from PGN comment */
16748 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16749 char *
16750 GetInfoFromComment (int index, char * text)
16751 {
16752     char * sep = text, *p;
16753
16754     if( text != NULL && index > 0 ) {
16755         int score = 0;
16756         int depth = 0;
16757         int time = -1, sec = 0, deci;
16758         char * s_eval = FindStr( text, "[%eval " );
16759         char * s_emt = FindStr( text, "[%emt " );
16760 #if 0
16761         if( s_eval != NULL || s_emt != NULL ) {
16762 #else
16763         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16764 #endif
16765             /* New style */
16766             char delim;
16767
16768             if( s_eval != NULL ) {
16769                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16770                     return text;
16771                 }
16772
16773                 if( delim != ']' ) {
16774                     return text;
16775                 }
16776             }
16777
16778             if( s_emt != NULL ) {
16779             }
16780                 return text;
16781         }
16782         else {
16783             /* We expect something like: [+|-]nnn.nn/dd */
16784             int score_lo = 0;
16785
16786             if(*text != '{') return text; // [HGM] braces: must be normal comment
16787
16788             sep = strchr( text, '/' );
16789             if( sep == NULL || sep < (text+4) ) {
16790                 return text;
16791             }
16792
16793             p = text;
16794             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16795             if(p[1] == '(') { // comment starts with PV
16796                p = strchr(p, ')'); // locate end of PV
16797                if(p == NULL || sep < p+5) return text;
16798                // at this point we have something like "{(.*) +0.23/6 ..."
16799                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16800                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16801                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16802             }
16803             time = -1; sec = -1; deci = -1;
16804             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16805                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16806                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16807                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16808                 return text;
16809             }
16810
16811             if( score_lo < 0 || score_lo >= 100 ) {
16812                 return text;
16813             }
16814
16815             if(sec >= 0) time = 600*time + 10*sec; else
16816             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16817
16818             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16819
16820             /* [HGM] PV time: now locate end of PV info */
16821             while( *++sep >= '0' && *sep <= '9'); // strip depth
16822             if(time >= 0)
16823             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16824             if(sec >= 0)
16825             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16826             if(deci >= 0)
16827             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16828             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16829         }
16830
16831         if( depth <= 0 ) {
16832             return text;
16833         }
16834
16835         if( time < 0 ) {
16836             time = -1;
16837         }
16838
16839         pvInfoList[index-1].depth = depth;
16840         pvInfoList[index-1].score = score;
16841         pvInfoList[index-1].time  = 10*time; // centi-sec
16842         if(*sep == '}') *sep = 0; else *--sep = '{';
16843         if(p != text) {
16844             while(*p++ = *sep++)
16845                                 ;
16846             sep = text;
16847         } // squeeze out space between PV and comment, and return both
16848     }
16849     return sep;
16850 }
16851
16852 void
16853 SendToProgram (char *message, ChessProgramState *cps)
16854 {
16855     int count, outCount, error;
16856     char buf[MSG_SIZ];
16857
16858     if (cps->pr == NoProc) return;
16859     Attention(cps);
16860
16861     if (appData.debugMode) {
16862         TimeMark now;
16863         GetTimeMark(&now);
16864         fprintf(debugFP, "%ld >%-6s: %s",
16865                 SubtractTimeMarks(&now, &programStartTime),
16866                 cps->which, message);
16867         if(serverFP)
16868             fprintf(serverFP, "%ld >%-6s: %s",
16869                 SubtractTimeMarks(&now, &programStartTime),
16870                 cps->which, message), fflush(serverFP);
16871     }
16872
16873     count = strlen(message);
16874     outCount = OutputToProcess(cps->pr, message, count, &error);
16875     if (outCount < count && !exiting
16876                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16877       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16878       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16879         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16880             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16881                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16882                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16883                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16884             } else {
16885                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16886                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16887                 gameInfo.result = res;
16888             }
16889             gameInfo.resultDetails = StrSave(buf);
16890         }
16891         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16892         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16893     }
16894 }
16895
16896 void
16897 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16898 {
16899     char *end_str;
16900     char buf[MSG_SIZ];
16901     ChessProgramState *cps = (ChessProgramState *)closure;
16902
16903     if (isr != cps->isr) return; /* Killed intentionally */
16904     if (count <= 0) {
16905         if (count == 0) {
16906             RemoveInputSource(cps->isr);
16907             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16908                     _(cps->which), cps->program);
16909             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16910             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16911                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16912                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16913                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16914                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16915                 } else {
16916                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16917                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16918                     gameInfo.result = res;
16919                 }
16920                 gameInfo.resultDetails = StrSave(buf);
16921             }
16922             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16923             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16924         } else {
16925             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16926                     _(cps->which), cps->program);
16927             RemoveInputSource(cps->isr);
16928
16929             /* [AS] Program is misbehaving badly... kill it */
16930             if( count == -2 ) {
16931                 DestroyChildProcess( cps->pr, 9 );
16932                 cps->pr = NoProc;
16933             }
16934
16935             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16936         }
16937         return;
16938     }
16939
16940     if ((end_str = strchr(message, '\r')) != NULL)
16941       *end_str = NULLCHAR;
16942     if ((end_str = strchr(message, '\n')) != NULL)
16943       *end_str = NULLCHAR;
16944
16945     if (appData.debugMode) {
16946         TimeMark now; int print = 1;
16947         char *quote = ""; char c; int i;
16948
16949         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16950                 char start = message[0];
16951                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16952                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16953                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16954                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16955                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16956                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16957                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16958                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16959                    sscanf(message, "hint: %c", &c)!=1 &&
16960                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16961                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16962                     print = (appData.engineComments >= 2);
16963                 }
16964                 message[0] = start; // restore original message
16965         }
16966         if(print) {
16967                 GetTimeMark(&now);
16968                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16969                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16970                         quote,
16971                         message);
16972                 if(serverFP)
16973                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16974                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16975                         quote,
16976                         message), fflush(serverFP);
16977         }
16978     }
16979
16980     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16981     if (appData.icsEngineAnalyze) {
16982         if (strstr(message, "whisper") != NULL ||
16983              strstr(message, "kibitz") != NULL ||
16984             strstr(message, "tellics") != NULL) return;
16985     }
16986
16987     HandleMachineMove(message, cps);
16988 }
16989
16990
16991 void
16992 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16993 {
16994     char buf[MSG_SIZ];
16995     int seconds;
16996
16997     if( timeControl_2 > 0 ) {
16998         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16999             tc = timeControl_2;
17000         }
17001     }
17002     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17003     inc /= cps->timeOdds;
17004     st  /= cps->timeOdds;
17005
17006     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17007
17008     if (st > 0) {
17009       /* Set exact time per move, normally using st command */
17010       if (cps->stKludge) {
17011         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17012         seconds = st % 60;
17013         if (seconds == 0) {
17014           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17015         } else {
17016           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17017         }
17018       } else {
17019         snprintf(buf, MSG_SIZ, "st %d\n", st);
17020       }
17021     } else {
17022       /* Set conventional or incremental time control, using level command */
17023       if (seconds == 0) {
17024         /* Note old gnuchess bug -- minutes:seconds used to not work.
17025            Fixed in later versions, but still avoid :seconds
17026            when seconds is 0. */
17027         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17028       } else {
17029         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17030                  seconds, inc/1000.);
17031       }
17032     }
17033     SendToProgram(buf, cps);
17034
17035     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17036     /* Orthogonally, limit search to given depth */
17037     if (sd > 0) {
17038       if (cps->sdKludge) {
17039         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17040       } else {
17041         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17042       }
17043       SendToProgram(buf, cps);
17044     }
17045
17046     if(cps->nps >= 0) { /* [HGM] nps */
17047         if(cps->supportsNPS == FALSE)
17048           cps->nps = -1; // don't use if engine explicitly says not supported!
17049         else {
17050           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17051           SendToProgram(buf, cps);
17052         }
17053     }
17054 }
17055
17056 ChessProgramState *
17057 WhitePlayer ()
17058 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17059 {
17060     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17061        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17062         return &second;
17063     return &first;
17064 }
17065
17066 void
17067 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17068 {
17069     char message[MSG_SIZ];
17070     long time, otime;
17071
17072     /* Note: this routine must be called when the clocks are stopped
17073        or when they have *just* been set or switched; otherwise
17074        it will be off by the time since the current tick started.
17075     */
17076     if (machineWhite) {
17077         time = whiteTimeRemaining / 10;
17078         otime = blackTimeRemaining / 10;
17079     } else {
17080         time = blackTimeRemaining / 10;
17081         otime = whiteTimeRemaining / 10;
17082     }
17083     /* [HGM] translate opponent's time by time-odds factor */
17084     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17085
17086     if (time <= 0) time = 1;
17087     if (otime <= 0) otime = 1;
17088
17089     snprintf(message, MSG_SIZ, "time %ld\n", time);
17090     SendToProgram(message, cps);
17091
17092     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17093     SendToProgram(message, cps);
17094 }
17095
17096 char *
17097 EngineDefinedVariant (ChessProgramState *cps, int n)
17098 {   // return name of n-th unknown variant that engine supports
17099     static char buf[MSG_SIZ];
17100     char *p, *s = cps->variants;
17101     if(!s) return NULL;
17102     do { // parse string from variants feature
17103       VariantClass v;
17104         p = strchr(s, ',');
17105         if(p) *p = NULLCHAR;
17106       v = StringToVariant(s);
17107       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17108         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17109             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17110                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17111                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17112                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17113             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17114         }
17115         if(p) *p++ = ',';
17116         if(n < 0) return buf;
17117     } while(s = p);
17118     return NULL;
17119 }
17120
17121 int
17122 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17123 {
17124   char buf[MSG_SIZ];
17125   int len = strlen(name);
17126   int val;
17127
17128   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17129     (*p) += len + 1;
17130     sscanf(*p, "%d", &val);
17131     *loc = (val != 0);
17132     while (**p && **p != ' ')
17133       (*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 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17143 {
17144   char buf[MSG_SIZ];
17145   int len = strlen(name);
17146   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17147     (*p) += len + 1;
17148     sscanf(*p, "%d", loc);
17149     while (**p && **p != ' ') (*p)++;
17150     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17151     SendToProgram(buf, cps);
17152     return TRUE;
17153   }
17154   return FALSE;
17155 }
17156
17157 int
17158 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17159 {
17160   char buf[MSG_SIZ];
17161   int len = strlen(name);
17162   if (strncmp((*p), name, len) == 0
17163       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17164     (*p) += len + 2;
17165     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17166     sscanf(*p, "%[^\"]", *loc);
17167     while (**p && **p != '\"') (*p)++;
17168     if (**p == '\"') (*p)++;
17169     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17170     SendToProgram(buf, cps);
17171     return TRUE;
17172   }
17173   return FALSE;
17174 }
17175
17176 int
17177 ParseOption (Option *opt, ChessProgramState *cps)
17178 // [HGM] options: process the string that defines an engine option, and determine
17179 // name, type, default value, and allowed value range
17180 {
17181         char *p, *q, buf[MSG_SIZ];
17182         int n, min = (-1)<<31, max = 1<<31, def;
17183
17184         opt->target = &opt->value;   // OK for spin/slider and checkbox
17185         if(p = strstr(opt->name, " -spin ")) {
17186             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17187             if(max < min) max = min; // enforce consistency
17188             if(def < min) def = min;
17189             if(def > max) def = max;
17190             opt->value = def;
17191             opt->min = min;
17192             opt->max = max;
17193             opt->type = Spin;
17194         } else if((p = strstr(opt->name, " -slider "))) {
17195             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17196             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17197             if(max < min) max = min; // enforce consistency
17198             if(def < min) def = min;
17199             if(def > max) def = max;
17200             opt->value = def;
17201             opt->min = min;
17202             opt->max = max;
17203             opt->type = Spin; // Slider;
17204         } else if((p = strstr(opt->name, " -string "))) {
17205             opt->textValue = p+9;
17206             opt->type = TextBox;
17207             opt->target = &opt->textValue;
17208         } else if((p = strstr(opt->name, " -file "))) {
17209             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17210             opt->target = opt->textValue = p+7;
17211             opt->type = FileName; // FileName;
17212             opt->target = &opt->textValue;
17213         } else if((p = strstr(opt->name, " -path "))) {
17214             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17215             opt->target = opt->textValue = p+7;
17216             opt->type = PathName; // PathName;
17217             opt->target = &opt->textValue;
17218         } else if(p = strstr(opt->name, " -check ")) {
17219             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17220             opt->value = (def != 0);
17221             opt->type = CheckBox;
17222         } else if(p = strstr(opt->name, " -combo ")) {
17223             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17224             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17225             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17226             opt->value = n = 0;
17227             while(q = StrStr(q, " /// ")) {
17228                 n++; *q = 0;    // count choices, and null-terminate each of them
17229                 q += 5;
17230                 if(*q == '*') { // remember default, which is marked with * prefix
17231                     q++;
17232                     opt->value = n;
17233                 }
17234                 cps->comboList[cps->comboCnt++] = q;
17235             }
17236             cps->comboList[cps->comboCnt++] = NULL;
17237             opt->max = n + 1;
17238             opt->type = ComboBox;
17239         } else if(p = strstr(opt->name, " -button")) {
17240             opt->type = Button;
17241         } else if(p = strstr(opt->name, " -save")) {
17242             opt->type = SaveButton;
17243         } else return FALSE;
17244         *p = 0; // terminate option name
17245         // now look if the command-line options define a setting for this engine option.
17246         if(cps->optionSettings && cps->optionSettings[0])
17247             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17248         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17249           snprintf(buf, MSG_SIZ, "option %s", p);
17250                 if(p = strstr(buf, ",")) *p = 0;
17251                 if(q = strchr(buf, '=')) switch(opt->type) {
17252                     case ComboBox:
17253                         for(n=0; n<opt->max; n++)
17254                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17255                         break;
17256                     case TextBox:
17257                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17258                         break;
17259                     case Spin:
17260                     case CheckBox:
17261                         opt->value = atoi(q+1);
17262                     default:
17263                         break;
17264                 }
17265                 strcat(buf, "\n");
17266                 SendToProgram(buf, cps);
17267         }
17268         return TRUE;
17269 }
17270
17271 void
17272 FeatureDone (ChessProgramState *cps, int val)
17273 {
17274   DelayedEventCallback cb = GetDelayedEvent();
17275   if ((cb == InitBackEnd3 && cps == &first) ||
17276       (cb == SettingsMenuIfReady && cps == &second) ||
17277       (cb == LoadEngine) ||
17278       (cb == TwoMachinesEventIfReady)) {
17279     CancelDelayedEvent();
17280     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17281   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17282   cps->initDone = val;
17283   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17284 }
17285
17286 /* Parse feature command from engine */
17287 void
17288 ParseFeatures (char *args, ChessProgramState *cps)
17289 {
17290   char *p = args;
17291   char *q = NULL;
17292   int val;
17293   char buf[MSG_SIZ];
17294
17295   for (;;) {
17296     while (*p == ' ') p++;
17297     if (*p == NULLCHAR) return;
17298
17299     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17300     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17301     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17302     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17303     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17304     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17305     if (BoolFeature(&p, "reuse", &val, cps)) {
17306       /* Engine can disable reuse, but can't enable it if user said no */
17307       if (!val) cps->reuse = FALSE;
17308       continue;
17309     }
17310     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17311     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17312       if (gameMode == TwoMachinesPlay) {
17313         DisplayTwoMachinesTitle();
17314       } else {
17315         DisplayTitle("");
17316       }
17317       continue;
17318     }
17319     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17320     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17321     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17322     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17323     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17324     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17325     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17326     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17327     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17328     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17329     if (IntFeature(&p, "done", &val, cps)) {
17330       FeatureDone(cps, val);
17331       continue;
17332     }
17333     /* Added by Tord: */
17334     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17335     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17336     /* End of additions by Tord */
17337
17338     /* [HGM] added features: */
17339     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17340     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17341     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17342     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17343     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17344     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17345     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17346     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17347         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17348         FREE(cps->option[cps->nrOptions].name);
17349         cps->option[cps->nrOptions].name = q; q = NULL;
17350         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17351           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17352             SendToProgram(buf, cps);
17353             continue;
17354         }
17355         if(cps->nrOptions >= MAX_OPTIONS) {
17356             cps->nrOptions--;
17357             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17358             DisplayError(buf, 0);
17359         }
17360         continue;
17361     }
17362     /* End of additions by HGM */
17363
17364     /* unknown feature: complain and skip */
17365     q = p;
17366     while (*q && *q != '=') q++;
17367     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17368     SendToProgram(buf, cps);
17369     p = q;
17370     if (*p == '=') {
17371       p++;
17372       if (*p == '\"') {
17373         p++;
17374         while (*p && *p != '\"') p++;
17375         if (*p == '\"') p++;
17376       } else {
17377         while (*p && *p != ' ') p++;
17378       }
17379     }
17380   }
17381
17382 }
17383
17384 void
17385 PeriodicUpdatesEvent (int newState)
17386 {
17387     if (newState == appData.periodicUpdates)
17388       return;
17389
17390     appData.periodicUpdates=newState;
17391
17392     /* Display type changes, so update it now */
17393 //    DisplayAnalysis();
17394
17395     /* Get the ball rolling again... */
17396     if (newState) {
17397         AnalysisPeriodicEvent(1);
17398         StartAnalysisClock();
17399     }
17400 }
17401
17402 void
17403 PonderNextMoveEvent (int newState)
17404 {
17405     if (newState == appData.ponderNextMove) return;
17406     if (gameMode == EditPosition) EditPositionDone(TRUE);
17407     if (newState) {
17408         SendToProgram("hard\n", &first);
17409         if (gameMode == TwoMachinesPlay) {
17410             SendToProgram("hard\n", &second);
17411         }
17412     } else {
17413         SendToProgram("easy\n", &first);
17414         thinkOutput[0] = NULLCHAR;
17415         if (gameMode == TwoMachinesPlay) {
17416             SendToProgram("easy\n", &second);
17417         }
17418     }
17419     appData.ponderNextMove = newState;
17420 }
17421
17422 void
17423 NewSettingEvent (int option, int *feature, char *command, int value)
17424 {
17425     char buf[MSG_SIZ];
17426
17427     if (gameMode == EditPosition) EditPositionDone(TRUE);
17428     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17429     if(feature == NULL || *feature) SendToProgram(buf, &first);
17430     if (gameMode == TwoMachinesPlay) {
17431         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17432     }
17433 }
17434
17435 void
17436 ShowThinkingEvent ()
17437 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17438 {
17439     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17440     int newState = appData.showThinking
17441         // [HGM] thinking: other features now need thinking output as well
17442         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17443
17444     if (oldState == newState) return;
17445     oldState = newState;
17446     if (gameMode == EditPosition) EditPositionDone(TRUE);
17447     if (oldState) {
17448         SendToProgram("post\n", &first);
17449         if (gameMode == TwoMachinesPlay) {
17450             SendToProgram("post\n", &second);
17451         }
17452     } else {
17453         SendToProgram("nopost\n", &first);
17454         thinkOutput[0] = NULLCHAR;
17455         if (gameMode == TwoMachinesPlay) {
17456             SendToProgram("nopost\n", &second);
17457         }
17458     }
17459 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17460 }
17461
17462 void
17463 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17464 {
17465   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17466   if (pr == NoProc) return;
17467   AskQuestion(title, question, replyPrefix, pr);
17468 }
17469
17470 void
17471 TypeInEvent (char firstChar)
17472 {
17473     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17474         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17475         gameMode == AnalyzeMode || gameMode == EditGame ||
17476         gameMode == EditPosition || gameMode == IcsExamining ||
17477         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17478         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17479                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17480                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17481         gameMode == Training) PopUpMoveDialog(firstChar);
17482 }
17483
17484 void
17485 TypeInDoneEvent (char *move)
17486 {
17487         Board board;
17488         int n, fromX, fromY, toX, toY;
17489         char promoChar;
17490         ChessMove moveType;
17491
17492         // [HGM] FENedit
17493         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17494                 EditPositionPasteFEN(move);
17495                 return;
17496         }
17497         // [HGM] movenum: allow move number to be typed in any mode
17498         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17499           ToNrEvent(2*n-1);
17500           return;
17501         }
17502         // undocumented kludge: allow command-line option to be typed in!
17503         // (potentially fatal, and does not implement the effect of the option.)
17504         // should only be used for options that are values on which future decisions will be made,
17505         // and definitely not on options that would be used during initialization.
17506         if(strstr(move, "!!! -") == move) {
17507             ParseArgsFromString(move+4);
17508             return;
17509         }
17510
17511       if (gameMode != EditGame && currentMove != forwardMostMove &&
17512         gameMode != Training) {
17513         DisplayMoveError(_("Displayed move is not current"));
17514       } else {
17515         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17516           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17517         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17518         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17519           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17520           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17521         } else {
17522           DisplayMoveError(_("Could not parse move"));
17523         }
17524       }
17525 }
17526
17527 void
17528 DisplayMove (int moveNumber)
17529 {
17530     char message[MSG_SIZ];
17531     char res[MSG_SIZ];
17532     char cpThinkOutput[MSG_SIZ];
17533
17534     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17535
17536     if (moveNumber == forwardMostMove - 1 ||
17537         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17538
17539         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17540
17541         if (strchr(cpThinkOutput, '\n')) {
17542             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17543         }
17544     } else {
17545         *cpThinkOutput = NULLCHAR;
17546     }
17547
17548     /* [AS] Hide thinking from human user */
17549     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17550         *cpThinkOutput = NULLCHAR;
17551         if( thinkOutput[0] != NULLCHAR ) {
17552             int i;
17553
17554             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17555                 cpThinkOutput[i] = '.';
17556             }
17557             cpThinkOutput[i] = NULLCHAR;
17558             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17559         }
17560     }
17561
17562     if (moveNumber == forwardMostMove - 1 &&
17563         gameInfo.resultDetails != NULL) {
17564         if (gameInfo.resultDetails[0] == NULLCHAR) {
17565           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17566         } else {
17567           snprintf(res, MSG_SIZ, " {%s} %s",
17568                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17569         }
17570     } else {
17571         res[0] = NULLCHAR;
17572     }
17573
17574     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17575         DisplayMessage(res, cpThinkOutput);
17576     } else {
17577       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17578                 WhiteOnMove(moveNumber) ? " " : ".. ",
17579                 parseList[moveNumber], res);
17580         DisplayMessage(message, cpThinkOutput);
17581     }
17582 }
17583
17584 void
17585 DisplayComment (int moveNumber, char *text)
17586 {
17587     char title[MSG_SIZ];
17588
17589     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17590       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17591     } else {
17592       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17593               WhiteOnMove(moveNumber) ? " " : ".. ",
17594               parseList[moveNumber]);
17595     }
17596     if (text != NULL && (appData.autoDisplayComment || commentUp))
17597         CommentPopUp(title, text);
17598 }
17599
17600 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17601  * might be busy thinking or pondering.  It can be omitted if your
17602  * gnuchess is configured to stop thinking immediately on any user
17603  * input.  However, that gnuchess feature depends on the FIONREAD
17604  * ioctl, which does not work properly on some flavors of Unix.
17605  */
17606 void
17607 Attention (ChessProgramState *cps)
17608 {
17609 #if ATTENTION
17610     if (!cps->useSigint) return;
17611     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17612     switch (gameMode) {
17613       case MachinePlaysWhite:
17614       case MachinePlaysBlack:
17615       case TwoMachinesPlay:
17616       case IcsPlayingWhite:
17617       case IcsPlayingBlack:
17618       case AnalyzeMode:
17619       case AnalyzeFile:
17620         /* Skip if we know it isn't thinking */
17621         if (!cps->maybeThinking) return;
17622         if (appData.debugMode)
17623           fprintf(debugFP, "Interrupting %s\n", cps->which);
17624         InterruptChildProcess(cps->pr);
17625         cps->maybeThinking = FALSE;
17626         break;
17627       default:
17628         break;
17629     }
17630 #endif /*ATTENTION*/
17631 }
17632
17633 int
17634 CheckFlags ()
17635 {
17636     if (whiteTimeRemaining <= 0) {
17637         if (!whiteFlag) {
17638             whiteFlag = TRUE;
17639             if (appData.icsActive) {
17640                 if (appData.autoCallFlag &&
17641                     gameMode == IcsPlayingBlack && !blackFlag) {
17642                   SendToICS(ics_prefix);
17643                   SendToICS("flag\n");
17644                 }
17645             } else {
17646                 if (blackFlag) {
17647                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17648                 } else {
17649                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17650                     if (appData.autoCallFlag) {
17651                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17652                         return TRUE;
17653                     }
17654                 }
17655             }
17656         }
17657     }
17658     if (blackTimeRemaining <= 0) {
17659         if (!blackFlag) {
17660             blackFlag = TRUE;
17661             if (appData.icsActive) {
17662                 if (appData.autoCallFlag &&
17663                     gameMode == IcsPlayingWhite && !whiteFlag) {
17664                   SendToICS(ics_prefix);
17665                   SendToICS("flag\n");
17666                 }
17667             } else {
17668                 if (whiteFlag) {
17669                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17670                 } else {
17671                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17672                     if (appData.autoCallFlag) {
17673                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17674                         return TRUE;
17675                     }
17676                 }
17677             }
17678         }
17679     }
17680     return FALSE;
17681 }
17682
17683 void
17684 CheckTimeControl ()
17685 {
17686     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17687         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17688
17689     /*
17690      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17691      */
17692     if ( !WhiteOnMove(forwardMostMove) ) {
17693         /* White made time control */
17694         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17695         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17696         /* [HGM] time odds: correct new time quota for time odds! */
17697                                             / WhitePlayer()->timeOdds;
17698         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17699     } else {
17700         lastBlack -= blackTimeRemaining;
17701         /* Black made time control */
17702         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17703                                             / WhitePlayer()->other->timeOdds;
17704         lastWhite = whiteTimeRemaining;
17705     }
17706 }
17707
17708 void
17709 DisplayBothClocks ()
17710 {
17711     int wom = gameMode == EditPosition ?
17712       !blackPlaysFirst : WhiteOnMove(currentMove);
17713     DisplayWhiteClock(whiteTimeRemaining, wom);
17714     DisplayBlackClock(blackTimeRemaining, !wom);
17715 }
17716
17717
17718 /* Timekeeping seems to be a portability nightmare.  I think everyone
17719    has ftime(), but I'm really not sure, so I'm including some ifdefs
17720    to use other calls if you don't.  Clocks will be less accurate if
17721    you have neither ftime nor gettimeofday.
17722 */
17723
17724 /* VS 2008 requires the #include outside of the function */
17725 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17726 #include <sys/timeb.h>
17727 #endif
17728
17729 /* Get the current time as a TimeMark */
17730 void
17731 GetTimeMark (TimeMark *tm)
17732 {
17733 #if HAVE_GETTIMEOFDAY
17734
17735     struct timeval timeVal;
17736     struct timezone timeZone;
17737
17738     gettimeofday(&timeVal, &timeZone);
17739     tm->sec = (long) timeVal.tv_sec;
17740     tm->ms = (int) (timeVal.tv_usec / 1000L);
17741
17742 #else /*!HAVE_GETTIMEOFDAY*/
17743 #if HAVE_FTIME
17744
17745 // include <sys/timeb.h> / moved to just above start of function
17746     struct timeb timeB;
17747
17748     ftime(&timeB);
17749     tm->sec = (long) timeB.time;
17750     tm->ms = (int) timeB.millitm;
17751
17752 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17753     tm->sec = (long) time(NULL);
17754     tm->ms = 0;
17755 #endif
17756 #endif
17757 }
17758
17759 /* Return the difference in milliseconds between two
17760    time marks.  We assume the difference will fit in a long!
17761 */
17762 long
17763 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17764 {
17765     return 1000L*(tm2->sec - tm1->sec) +
17766            (long) (tm2->ms - tm1->ms);
17767 }
17768
17769
17770 /*
17771  * Code to manage the game clocks.
17772  *
17773  * In tournament play, black starts the clock and then white makes a move.
17774  * We give the human user a slight advantage if he is playing white---the
17775  * clocks don't run until he makes his first move, so it takes zero time.
17776  * Also, we don't account for network lag, so we could get out of sync
17777  * with GNU Chess's clock -- but then, referees are always right.
17778  */
17779
17780 static TimeMark tickStartTM;
17781 static long intendedTickLength;
17782
17783 long
17784 NextTickLength (long timeRemaining)
17785 {
17786     long nominalTickLength, nextTickLength;
17787
17788     if (timeRemaining > 0L && timeRemaining <= 10000L)
17789       nominalTickLength = 100L;
17790     else
17791       nominalTickLength = 1000L;
17792     nextTickLength = timeRemaining % nominalTickLength;
17793     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17794
17795     return nextTickLength;
17796 }
17797
17798 /* Adjust clock one minute up or down */
17799 void
17800 AdjustClock (Boolean which, int dir)
17801 {
17802     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17803     if(which) blackTimeRemaining += 60000*dir;
17804     else      whiteTimeRemaining += 60000*dir;
17805     DisplayBothClocks();
17806     adjustedClock = TRUE;
17807 }
17808
17809 /* Stop clocks and reset to a fresh time control */
17810 void
17811 ResetClocks ()
17812 {
17813     (void) StopClockTimer();
17814     if (appData.icsActive) {
17815         whiteTimeRemaining = blackTimeRemaining = 0;
17816     } else if (searchTime) {
17817         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17818         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17819     } else { /* [HGM] correct new time quote for time odds */
17820         whiteTC = blackTC = fullTimeControlString;
17821         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17822         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17823     }
17824     if (whiteFlag || blackFlag) {
17825         DisplayTitle("");
17826         whiteFlag = blackFlag = FALSE;
17827     }
17828     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17829     DisplayBothClocks();
17830     adjustedClock = FALSE;
17831 }
17832
17833 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17834
17835 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17836
17837 /* Decrement running clock by amount of time that has passed */
17838 void
17839 DecrementClocks ()
17840 {
17841     long tRemaining;
17842     long lastTickLength, fudge;
17843     TimeMark now;
17844
17845     if (!appData.clockMode) return;
17846     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17847
17848     GetTimeMark(&now);
17849
17850     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17851
17852     /* Fudge if we woke up a little too soon */
17853     fudge = intendedTickLength - lastTickLength;
17854     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17855
17856     if (WhiteOnMove(forwardMostMove)) {
17857         if(whiteNPS >= 0) lastTickLength = 0;
17858          tRemaining = whiteTimeRemaining -= lastTickLength;
17859         if( tRemaining < 0 && !appData.icsActive) {
17860             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17861             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17862                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17863                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17864             }
17865         }
17866         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17867         DisplayWhiteClock(whiteTimeRemaining - fudge,
17868                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17869         timeSuffix = 0;
17870     } else {
17871         if(blackNPS >= 0) lastTickLength = 0;
17872          tRemaining = blackTimeRemaining -= lastTickLength;
17873         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17874             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17875             if(suddenDeath) {
17876                 blackStartMove = forwardMostMove;
17877                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17878             }
17879         }
17880         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17881         DisplayBlackClock(blackTimeRemaining - fudge,
17882                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17883         timeSuffix = 0;
17884     }
17885     if (CheckFlags()) return;
17886
17887     if(twoBoards) { // count down secondary board's clocks as well
17888         activePartnerTime -= lastTickLength;
17889         partnerUp = 1;
17890         if(activePartner == 'W')
17891             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17892         else
17893             DisplayBlackClock(activePartnerTime, TRUE);
17894         partnerUp = 0;
17895     }
17896
17897     tickStartTM = now;
17898     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17899     StartClockTimer(intendedTickLength);
17900
17901     /* if the time remaining has fallen below the alarm threshold, sound the
17902      * alarm. if the alarm has sounded and (due to a takeback or time control
17903      * with increment) the time remaining has increased to a level above the
17904      * threshold, reset the alarm so it can sound again.
17905      */
17906
17907     if (appData.icsActive && appData.icsAlarm) {
17908
17909         /* make sure we are dealing with the user's clock */
17910         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17911                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17912            )) return;
17913
17914         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17915             alarmSounded = FALSE;
17916         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17917             PlayAlarmSound();
17918             alarmSounded = TRUE;
17919         }
17920     }
17921 }
17922
17923
17924 /* A player has just moved, so stop the previously running
17925    clock and (if in clock mode) start the other one.
17926    We redisplay both clocks in case we're in ICS mode, because
17927    ICS gives us an update to both clocks after every move.
17928    Note that this routine is called *after* forwardMostMove
17929    is updated, so the last fractional tick must be subtracted
17930    from the color that is *not* on move now.
17931 */
17932 void
17933 SwitchClocks (int newMoveNr)
17934 {
17935     long lastTickLength;
17936     TimeMark now;
17937     int flagged = FALSE;
17938
17939     GetTimeMark(&now);
17940
17941     if (StopClockTimer() && appData.clockMode) {
17942         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17943         if (!WhiteOnMove(forwardMostMove)) {
17944             if(blackNPS >= 0) lastTickLength = 0;
17945             blackTimeRemaining -= lastTickLength;
17946            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17947 //         if(pvInfoList[forwardMostMove].time == -1)
17948                  pvInfoList[forwardMostMove].time =               // use GUI time
17949                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17950         } else {
17951            if(whiteNPS >= 0) lastTickLength = 0;
17952            whiteTimeRemaining -= lastTickLength;
17953            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17954 //         if(pvInfoList[forwardMostMove].time == -1)
17955                  pvInfoList[forwardMostMove].time =
17956                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17957         }
17958         flagged = CheckFlags();
17959     }
17960     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17961     CheckTimeControl();
17962
17963     if (flagged || !appData.clockMode) return;
17964
17965     switch (gameMode) {
17966       case MachinePlaysBlack:
17967       case MachinePlaysWhite:
17968       case BeginningOfGame:
17969         if (pausing) return;
17970         break;
17971
17972       case EditGame:
17973       case PlayFromGameFile:
17974       case IcsExamining:
17975         return;
17976
17977       default:
17978         break;
17979     }
17980
17981     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17982         if(WhiteOnMove(forwardMostMove))
17983              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17984         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17985     }
17986
17987     tickStartTM = now;
17988     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17989       whiteTimeRemaining : blackTimeRemaining);
17990     StartClockTimer(intendedTickLength);
17991 }
17992
17993
17994 /* Stop both clocks */
17995 void
17996 StopClocks ()
17997 {
17998     long lastTickLength;
17999     TimeMark now;
18000
18001     if (!StopClockTimer()) return;
18002     if (!appData.clockMode) return;
18003
18004     GetTimeMark(&now);
18005
18006     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18007     if (WhiteOnMove(forwardMostMove)) {
18008         if(whiteNPS >= 0) lastTickLength = 0;
18009         whiteTimeRemaining -= lastTickLength;
18010         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18011     } else {
18012         if(blackNPS >= 0) lastTickLength = 0;
18013         blackTimeRemaining -= lastTickLength;
18014         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18015     }
18016     CheckFlags();
18017 }
18018
18019 /* Start clock of player on move.  Time may have been reset, so
18020    if clock is already running, stop and restart it. */
18021 void
18022 StartClocks ()
18023 {
18024     (void) StopClockTimer(); /* in case it was running already */
18025     DisplayBothClocks();
18026     if (CheckFlags()) return;
18027
18028     if (!appData.clockMode) return;
18029     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18030
18031     GetTimeMark(&tickStartTM);
18032     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18033       whiteTimeRemaining : blackTimeRemaining);
18034
18035    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18036     whiteNPS = blackNPS = -1;
18037     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18038        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18039         whiteNPS = first.nps;
18040     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18041        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18042         blackNPS = first.nps;
18043     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18044         whiteNPS = second.nps;
18045     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18046         blackNPS = second.nps;
18047     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18048
18049     StartClockTimer(intendedTickLength);
18050 }
18051
18052 char *
18053 TimeString (long ms)
18054 {
18055     long second, minute, hour, day;
18056     char *sign = "";
18057     static char buf[40], moveTime[8];
18058
18059     if (ms > 0 && ms <= 9900) {
18060       /* convert milliseconds to tenths, rounding up */
18061       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18062
18063       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18064       return buf;
18065     }
18066
18067     /* convert milliseconds to seconds, rounding up */
18068     /* use floating point to avoid strangeness of integer division
18069        with negative dividends on many machines */
18070     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18071
18072     if (second < 0) {
18073         sign = "-";
18074         second = -second;
18075     }
18076
18077     day = second / (60 * 60 * 24);
18078     second = second % (60 * 60 * 24);
18079     hour = second / (60 * 60);
18080     second = second % (60 * 60);
18081     minute = second / 60;
18082     second = second % 60;
18083
18084     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18085     else *moveTime = NULLCHAR;
18086
18087     if (day > 0)
18088       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18089               sign, day, hour, minute, second, moveTime);
18090     else if (hour > 0)
18091       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18092     else
18093       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18094
18095     return buf;
18096 }
18097
18098
18099 /*
18100  * This is necessary because some C libraries aren't ANSI C compliant yet.
18101  */
18102 char *
18103 StrStr (char *string, char *match)
18104 {
18105     int i, length;
18106
18107     length = strlen(match);
18108
18109     for (i = strlen(string) - length; i >= 0; i--, string++)
18110       if (!strncmp(match, string, length))
18111         return string;
18112
18113     return NULL;
18114 }
18115
18116 char *
18117 StrCaseStr (char *string, char *match)
18118 {
18119     int i, j, length;
18120
18121     length = strlen(match);
18122
18123     for (i = strlen(string) - length; i >= 0; i--, string++) {
18124         for (j = 0; j < length; j++) {
18125             if (ToLower(match[j]) != ToLower(string[j]))
18126               break;
18127         }
18128         if (j == length) return string;
18129     }
18130
18131     return NULL;
18132 }
18133
18134 #ifndef _amigados
18135 int
18136 StrCaseCmp (char *s1, char *s2)
18137 {
18138     char c1, c2;
18139
18140     for (;;) {
18141         c1 = ToLower(*s1++);
18142         c2 = ToLower(*s2++);
18143         if (c1 > c2) return 1;
18144         if (c1 < c2) return -1;
18145         if (c1 == NULLCHAR) return 0;
18146     }
18147 }
18148
18149
18150 int
18151 ToLower (int c)
18152 {
18153     return isupper(c) ? tolower(c) : c;
18154 }
18155
18156
18157 int
18158 ToUpper (int c)
18159 {
18160     return islower(c) ? toupper(c) : c;
18161 }
18162 #endif /* !_amigados    */
18163
18164 char *
18165 StrSave (char *s)
18166 {
18167   char *ret;
18168
18169   if ((ret = (char *) malloc(strlen(s) + 1)))
18170     {
18171       safeStrCpy(ret, s, strlen(s)+1);
18172     }
18173   return ret;
18174 }
18175
18176 char *
18177 StrSavePtr (char *s, char **savePtr)
18178 {
18179     if (*savePtr) {
18180         free(*savePtr);
18181     }
18182     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18183       safeStrCpy(*savePtr, s, strlen(s)+1);
18184     }
18185     return(*savePtr);
18186 }
18187
18188 char *
18189 PGNDate ()
18190 {
18191     time_t clock;
18192     struct tm *tm;
18193     char buf[MSG_SIZ];
18194
18195     clock = time((time_t *)NULL);
18196     tm = localtime(&clock);
18197     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18198             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18199     return StrSave(buf);
18200 }
18201
18202
18203 char *
18204 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18205 {
18206     int i, j, fromX, fromY, toX, toY;
18207     int whiteToPlay, haveRights = nrCastlingRights;
18208     char buf[MSG_SIZ];
18209     char *p, *q;
18210     int emptycount;
18211     ChessSquare piece;
18212
18213     whiteToPlay = (gameMode == EditPosition) ?
18214       !blackPlaysFirst : (move % 2 == 0);
18215     p = buf;
18216
18217     /* Piece placement data */
18218     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18219         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18220         emptycount = 0;
18221         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18222             if (boards[move][i][j] == EmptySquare) {
18223                 emptycount++;
18224             } else { ChessSquare piece = boards[move][i][j];
18225                 if (emptycount > 0) {
18226                     if(emptycount<10) /* [HGM] can be >= 10 */
18227                         *p++ = '0' + emptycount;
18228                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18229                     emptycount = 0;
18230                 }
18231                 if(PieceToChar(piece) == '+') {
18232                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18233                     *p++ = '+';
18234                     piece = (ChessSquare)(CHUDEMOTED(piece));
18235                 }
18236                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18237                 if(*p = PieceSuffix(piece)) p++;
18238                 if(p[-1] == '~') {
18239                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18240                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18241                     *p++ = '~';
18242                 }
18243             }
18244         }
18245         if (emptycount > 0) {
18246             if(emptycount<10) /* [HGM] can be >= 10 */
18247                 *p++ = '0' + emptycount;
18248             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18249             emptycount = 0;
18250         }
18251         *p++ = '/';
18252     }
18253     *(p - 1) = ' ';
18254
18255     /* [HGM] print Crazyhouse or Shogi holdings */
18256     if( gameInfo.holdingsWidth ) {
18257         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18258         q = p;
18259         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18260             piece = boards[move][i][BOARD_WIDTH-1];
18261             if( piece != EmptySquare )
18262               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18263                   *p++ = PieceToChar(piece);
18264         }
18265         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18266             piece = boards[move][BOARD_HEIGHT-i-1][0];
18267             if( piece != EmptySquare )
18268               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18269                   *p++ = PieceToChar(piece);
18270         }
18271
18272         if( q == p ) *p++ = '-';
18273         *p++ = ']';
18274         *p++ = ' ';
18275     }
18276
18277     /* Active color */
18278     *p++ = whiteToPlay ? 'w' : 'b';
18279     *p++ = ' ';
18280
18281   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18282     haveRights = 0; q = p;
18283     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18284       piece = boards[move][0][i];
18285       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18286         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18287       }
18288     }
18289     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18290       piece = boards[move][BOARD_HEIGHT-1][i];
18291       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18292         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18293       }
18294     }
18295     if(p == q) *p++ = '-';
18296     *p++ = ' ';
18297   }
18298
18299   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18300     while(*p++ = *q++)
18301                       ;
18302     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18303   } else {
18304   if(haveRights) {
18305      int handW=0, handB=0;
18306      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18307         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18308         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18309      }
18310      q = p;
18311      if(appData.fischerCastling) {
18312         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18313            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18314                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18315         } else {
18316        /* [HGM] write directly from rights */
18317            if(boards[move][CASTLING][2] != NoRights &&
18318               boards[move][CASTLING][0] != NoRights   )
18319                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18320            if(boards[move][CASTLING][2] != NoRights &&
18321               boards[move][CASTLING][1] != NoRights   )
18322                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18323         }
18324         if(handB) {
18325            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18326                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18327         } else {
18328            if(boards[move][CASTLING][5] != NoRights &&
18329               boards[move][CASTLING][3] != NoRights   )
18330                 *p++ = boards[move][CASTLING][3] + AAA;
18331            if(boards[move][CASTLING][5] != NoRights &&
18332               boards[move][CASTLING][4] != NoRights   )
18333                 *p++ = boards[move][CASTLING][4] + AAA;
18334         }
18335      } else {
18336
18337         /* [HGM] write true castling rights */
18338         if( nrCastlingRights == 6 ) {
18339             int q, k=0;
18340             if(boards[move][CASTLING][0] != NoRights &&
18341                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18342             q = (boards[move][CASTLING][1] != NoRights &&
18343                  boards[move][CASTLING][2] != NoRights  );
18344             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18345                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18346                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18347                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18348             }
18349             if(q) *p++ = 'Q';
18350             k = 0;
18351             if(boards[move][CASTLING][3] != NoRights &&
18352                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18353             q = (boards[move][CASTLING][4] != NoRights &&
18354                  boards[move][CASTLING][5] != NoRights  );
18355             if(handB) {
18356                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18357                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18358                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18359             }
18360             if(q) *p++ = 'q';
18361         }
18362      }
18363      if (q == p) *p++ = '-'; /* No castling rights */
18364      *p++ = ' ';
18365   }
18366
18367   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18368      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18369      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18370     /* En passant target square */
18371     if (move > backwardMostMove) {
18372         fromX = moveList[move - 1][0] - AAA;
18373         fromY = moveList[move - 1][1] - ONE;
18374         toX = moveList[move - 1][2] - AAA;
18375         toY = moveList[move - 1][3] - ONE;
18376         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18377             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18378             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18379             fromX == toX) {
18380             /* 2-square pawn move just happened */
18381             *p++ = toX + AAA;
18382             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18383         } else {
18384             *p++ = '-';
18385         }
18386     } else if(move == backwardMostMove) {
18387         // [HGM] perhaps we should always do it like this, and forget the above?
18388         if((signed char)boards[move][EP_STATUS] >= 0) {
18389             *p++ = boards[move][EP_STATUS] + AAA;
18390             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18391         } else {
18392             *p++ = '-';
18393         }
18394     } else {
18395         *p++ = '-';
18396     }
18397     *p++ = ' ';
18398   }
18399   }
18400
18401     if(moveCounts)
18402     {   int i = 0, j=move;
18403
18404         /* [HGM] find reversible plies */
18405         if (appData.debugMode) { int k;
18406             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18407             for(k=backwardMostMove; k<=forwardMostMove; k++)
18408                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18409
18410         }
18411
18412         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18413         if( j == backwardMostMove ) i += initialRulePlies;
18414         sprintf(p, "%d ", i);
18415         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18416
18417         /* Fullmove number */
18418         sprintf(p, "%d", (move / 2) + 1);
18419     } else *--p = NULLCHAR;
18420
18421     return StrSave(buf);
18422 }
18423
18424 Boolean
18425 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18426 {
18427     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18428     char *p, c;
18429     int emptycount, virgin[BOARD_FILES];
18430     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18431
18432     p = fen;
18433
18434     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18435
18436     /* Piece placement data */
18437     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18438         j = 0;
18439         for (;;) {
18440             if (*p == '/' || *p == ' ' || *p == '[' ) {
18441                 if(j > w) w = j;
18442                 emptycount = gameInfo.boardWidth - j;
18443                 while (emptycount--)
18444                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18445                 if (*p == '/') p++;
18446                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18447                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18448                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18449                     }
18450                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18451                 }
18452                 break;
18453 #if(BOARD_FILES >= 10)*0
18454             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18455                 p++; emptycount=10;
18456                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18457                 while (emptycount--)
18458                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18459 #endif
18460             } else if (*p == '*') {
18461                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18462             } else if (isdigit(*p)) {
18463                 emptycount = *p++ - '0';
18464                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18465                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18466                 while (emptycount--)
18467                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18468             } else if (*p == '<') {
18469                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18470                 else if (i != 0 || !shuffle) return FALSE;
18471                 p++;
18472             } else if (shuffle && *p == '>') {
18473                 p++; // for now ignore closing shuffle range, and assume rank-end
18474             } else if (*p == '?') {
18475                 if (j >= gameInfo.boardWidth) return FALSE;
18476                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18477                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18478             } else if (*p == '+' || isalpha(*p)) {
18479                 char *q, *s = SUFFIXES;
18480                 if (j >= gameInfo.boardWidth) return FALSE;
18481                 if(*p=='+') {
18482                     char c = *++p;
18483                     if(q = strchr(s, p[1])) p++;
18484                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18485                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18486                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18487                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18488                 } else {
18489                     char c = *p++;
18490                     if(q = strchr(s, *p)) p++;
18491                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18492                 }
18493
18494                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18495                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18496                     piece = (ChessSquare) (PROMOTED(piece));
18497                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18498                     p++;
18499                 }
18500                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18501                 if(piece == king) wKingRank = i;
18502                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18503             } else {
18504                 return FALSE;
18505             }
18506         }
18507     }
18508     while (*p == '/' || *p == ' ') p++;
18509
18510     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18511
18512     /* [HGM] by default clear Crazyhouse holdings, if present */
18513     if(gameInfo.holdingsWidth) {
18514        for(i=0; i<BOARD_HEIGHT; i++) {
18515            board[i][0]             = EmptySquare; /* black holdings */
18516            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18517            board[i][1]             = (ChessSquare) 0; /* black counts */
18518            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18519        }
18520     }
18521
18522     /* [HGM] look for Crazyhouse holdings here */
18523     while(*p==' ') p++;
18524     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18525         int swap=0, wcnt=0, bcnt=0;
18526         if(*p == '[') p++;
18527         if(*p == '<') swap++, p++;
18528         if(*p == '-' ) p++; /* empty holdings */ else {
18529             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18530             /* if we would allow FEN reading to set board size, we would   */
18531             /* have to add holdings and shift the board read so far here   */
18532             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18533                 p++;
18534                 if((int) piece >= (int) BlackPawn ) {
18535                     i = (int)piece - (int)BlackPawn;
18536                     i = PieceToNumber((ChessSquare)i);
18537                     if( i >= gameInfo.holdingsSize ) return FALSE;
18538                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18539                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18540                     bcnt++;
18541                 } else {
18542                     i = (int)piece - (int)WhitePawn;
18543                     i = PieceToNumber((ChessSquare)i);
18544                     if( i >= gameInfo.holdingsSize ) return FALSE;
18545                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18546                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18547                     wcnt++;
18548                 }
18549             }
18550             if(subst) { // substitute back-rank question marks by holdings pieces
18551                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18552                     int k, m, n = bcnt + 1;
18553                     if(board[0][j] == ClearBoard) {
18554                         if(!wcnt) return FALSE;
18555                         n = rand() % wcnt;
18556                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18557                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18558                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18559                             break;
18560                         }
18561                     }
18562                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18563                         if(!bcnt) return FALSE;
18564                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18565                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18566                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18567                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18568                             break;
18569                         }
18570                     }
18571                 }
18572                 subst = 0;
18573             }
18574         }
18575         if(*p == ']') p++;
18576     }
18577
18578     if(subst) return FALSE; // substitution requested, but no holdings
18579
18580     while(*p == ' ') p++;
18581
18582     /* Active color */
18583     c = *p++;
18584     if(appData.colorNickNames) {
18585       if( c == appData.colorNickNames[0] ) c = 'w'; else
18586       if( c == appData.colorNickNames[1] ) c = 'b';
18587     }
18588     switch (c) {
18589       case 'w':
18590         *blackPlaysFirst = FALSE;
18591         break;
18592       case 'b':
18593         *blackPlaysFirst = TRUE;
18594         break;
18595       default:
18596         return FALSE;
18597     }
18598
18599     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18600     /* return the extra info in global variiables             */
18601
18602     while(*p==' ') p++;
18603
18604     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18605         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18606         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18607     }
18608
18609     /* set defaults in case FEN is incomplete */
18610     board[EP_STATUS] = EP_UNKNOWN;
18611     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18612     for(i=0; i<nrCastlingRights; i++ ) {
18613         board[CASTLING][i] =
18614             appData.fischerCastling ? NoRights : initialRights[i];
18615     }   /* assume possible unless obviously impossible */
18616     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18617     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18618     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18619                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18620     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18621     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18622     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18623                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18624     FENrulePlies = 0;
18625
18626     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18627       char *q = p;
18628       int w=0, b=0;
18629       while(isalpha(*p)) {
18630         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18631         if(islower(*p)) b |= 1 << (*p++ - 'a');
18632       }
18633       if(*p == '-') p++;
18634       if(p != q) {
18635         board[TOUCHED_W] = ~w;
18636         board[TOUCHED_B] = ~b;
18637         while(*p == ' ') p++;
18638       }
18639     } else
18640
18641     if(nrCastlingRights) {
18642       int fischer = 0;
18643       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18644       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18645           /* castling indicator present, so default becomes no castlings */
18646           for(i=0; i<nrCastlingRights; i++ ) {
18647                  board[CASTLING][i] = NoRights;
18648           }
18649       }
18650       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18651              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18652              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18653              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18654         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18655
18656         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18657             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18658             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18659         }
18660         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18661             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18662         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18663                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18664         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18665                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18666         switch(c) {
18667           case'K':
18668               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18669               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18670               board[CASTLING][2] = whiteKingFile;
18671               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18672               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18673               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18674               break;
18675           case'Q':
18676               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18677               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18678               board[CASTLING][2] = whiteKingFile;
18679               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18680               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18681               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18682               break;
18683           case'k':
18684               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18685               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18686               board[CASTLING][5] = blackKingFile;
18687               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18688               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18689               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18690               break;
18691           case'q':
18692               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18693               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18694               board[CASTLING][5] = blackKingFile;
18695               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18696               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18697               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18698           case '-':
18699               break;
18700           default: /* FRC castlings */
18701               if(c >= 'a') { /* black rights */
18702                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18703                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18704                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18705                   if(i == BOARD_RGHT) break;
18706                   board[CASTLING][5] = i;
18707                   c -= AAA;
18708                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18709                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18710                   if(c > i)
18711                       board[CASTLING][3] = c;
18712                   else
18713                       board[CASTLING][4] = c;
18714               } else { /* white rights */
18715                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18716                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18717                     if(board[0][i] == WhiteKing) break;
18718                   if(i == BOARD_RGHT) break;
18719                   board[CASTLING][2] = i;
18720                   c -= AAA - 'a' + 'A';
18721                   if(board[0][c] >= WhiteKing) break;
18722                   if(c > i)
18723                       board[CASTLING][0] = c;
18724                   else
18725                       board[CASTLING][1] = c;
18726               }
18727         }
18728       }
18729       for(i=0; i<nrCastlingRights; i++)
18730         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18731       if(gameInfo.variant == VariantSChess)
18732         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18733       if(fischer && shuffle) appData.fischerCastling = TRUE;
18734     if (appData.debugMode) {
18735         fprintf(debugFP, "FEN castling rights:");
18736         for(i=0; i<nrCastlingRights; i++)
18737         fprintf(debugFP, " %d", board[CASTLING][i]);
18738         fprintf(debugFP, "\n");
18739     }
18740
18741       while(*p==' ') p++;
18742     }
18743
18744     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18745
18746     /* read e.p. field in games that know e.p. capture */
18747     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18748        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18749        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18750       if(*p=='-') {
18751         p++; board[EP_STATUS] = EP_NONE;
18752       } else {
18753          char c = *p++ - AAA;
18754
18755          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18756          if(*p >= '0' && *p <='9') p++;
18757          board[EP_STATUS] = c;
18758       }
18759     }
18760
18761
18762     if(sscanf(p, "%d", &i) == 1) {
18763         FENrulePlies = i; /* 50-move ply counter */
18764         /* (The move number is still ignored)    */
18765     }
18766
18767     return TRUE;
18768 }
18769
18770 void
18771 EditPositionPasteFEN (char *fen)
18772 {
18773   if (fen != NULL) {
18774     Board initial_position;
18775
18776     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18777       DisplayError(_("Bad FEN position in clipboard"), 0);
18778       return ;
18779     } else {
18780       int savedBlackPlaysFirst = blackPlaysFirst;
18781       EditPositionEvent();
18782       blackPlaysFirst = savedBlackPlaysFirst;
18783       CopyBoard(boards[0], initial_position);
18784       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18785       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18786       DisplayBothClocks();
18787       DrawPosition(FALSE, boards[currentMove]);
18788     }
18789   }
18790 }
18791
18792 static char cseq[12] = "\\   ";
18793
18794 Boolean
18795 set_cont_sequence (char *new_seq)
18796 {
18797     int len;
18798     Boolean ret;
18799
18800     // handle bad attempts to set the sequence
18801         if (!new_seq)
18802                 return 0; // acceptable error - no debug
18803
18804     len = strlen(new_seq);
18805     ret = (len > 0) && (len < sizeof(cseq));
18806     if (ret)
18807       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18808     else if (appData.debugMode)
18809       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18810     return ret;
18811 }
18812
18813 /*
18814     reformat a source message so words don't cross the width boundary.  internal
18815     newlines are not removed.  returns the wrapped size (no null character unless
18816     included in source message).  If dest is NULL, only calculate the size required
18817     for the dest buffer.  lp argument indicats line position upon entry, and it's
18818     passed back upon exit.
18819 */
18820 int
18821 wrap (char *dest, char *src, int count, int width, int *lp)
18822 {
18823     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18824
18825     cseq_len = strlen(cseq);
18826     old_line = line = *lp;
18827     ansi = len = clen = 0;
18828
18829     for (i=0; i < count; i++)
18830     {
18831         if (src[i] == '\033')
18832             ansi = 1;
18833
18834         // if we hit the width, back up
18835         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18836         {
18837             // store i & len in case the word is too long
18838             old_i = i, old_len = len;
18839
18840             // find the end of the last word
18841             while (i && src[i] != ' ' && src[i] != '\n')
18842             {
18843                 i--;
18844                 len--;
18845             }
18846
18847             // word too long?  restore i & len before splitting it
18848             if ((old_i-i+clen) >= width)
18849             {
18850                 i = old_i;
18851                 len = old_len;
18852             }
18853
18854             // extra space?
18855             if (i && src[i-1] == ' ')
18856                 len--;
18857
18858             if (src[i] != ' ' && src[i] != '\n')
18859             {
18860                 i--;
18861                 if (len)
18862                     len--;
18863             }
18864
18865             // now append the newline and continuation sequence
18866             if (dest)
18867                 dest[len] = '\n';
18868             len++;
18869             if (dest)
18870                 strncpy(dest+len, cseq, cseq_len);
18871             len += cseq_len;
18872             line = cseq_len;
18873             clen = cseq_len;
18874             continue;
18875         }
18876
18877         if (dest)
18878             dest[len] = src[i];
18879         len++;
18880         if (!ansi)
18881             line++;
18882         if (src[i] == '\n')
18883             line = 0;
18884         if (src[i] == 'm')
18885             ansi = 0;
18886     }
18887     if (dest && appData.debugMode)
18888     {
18889         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18890             count, width, line, len, *lp);
18891         show_bytes(debugFP, src, count);
18892         fprintf(debugFP, "\ndest: ");
18893         show_bytes(debugFP, dest, len);
18894         fprintf(debugFP, "\n");
18895     }
18896     *lp = dest ? line : old_line;
18897
18898     return len;
18899 }
18900
18901 // [HGM] vari: routines for shelving variations
18902 Boolean modeRestore = FALSE;
18903
18904 void
18905 PushInner (int firstMove, int lastMove)
18906 {
18907         int i, j, nrMoves = lastMove - firstMove;
18908
18909         // push current tail of game on stack
18910         savedResult[storedGames] = gameInfo.result;
18911         savedDetails[storedGames] = gameInfo.resultDetails;
18912         gameInfo.resultDetails = NULL;
18913         savedFirst[storedGames] = firstMove;
18914         savedLast [storedGames] = lastMove;
18915         savedFramePtr[storedGames] = framePtr;
18916         framePtr -= nrMoves; // reserve space for the boards
18917         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18918             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18919             for(j=0; j<MOVE_LEN; j++)
18920                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18921             for(j=0; j<2*MOVE_LEN; j++)
18922                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18923             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18924             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18925             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18926             pvInfoList[firstMove+i-1].depth = 0;
18927             commentList[framePtr+i] = commentList[firstMove+i];
18928             commentList[firstMove+i] = NULL;
18929         }
18930
18931         storedGames++;
18932         forwardMostMove = firstMove; // truncate game so we can start variation
18933 }
18934
18935 void
18936 PushTail (int firstMove, int lastMove)
18937 {
18938         if(appData.icsActive) { // only in local mode
18939                 forwardMostMove = currentMove; // mimic old ICS behavior
18940                 return;
18941         }
18942         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18943
18944         PushInner(firstMove, lastMove);
18945         if(storedGames == 1) GreyRevert(FALSE);
18946         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18947 }
18948
18949 void
18950 PopInner (Boolean annotate)
18951 {
18952         int i, j, nrMoves;
18953         char buf[8000], moveBuf[20];
18954
18955         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18956         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18957         nrMoves = savedLast[storedGames] - currentMove;
18958         if(annotate) {
18959                 int cnt = 10;
18960                 if(!WhiteOnMove(currentMove))
18961                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18962                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18963                 for(i=currentMove; i<forwardMostMove; i++) {
18964                         if(WhiteOnMove(i))
18965                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18966                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18967                         strcat(buf, moveBuf);
18968                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18969                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18970                 }
18971                 strcat(buf, ")");
18972         }
18973         for(i=1; i<=nrMoves; i++) { // copy last variation back
18974             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18975             for(j=0; j<MOVE_LEN; j++)
18976                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18977             for(j=0; j<2*MOVE_LEN; j++)
18978                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18979             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18980             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18981             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18982             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18983             commentList[currentMove+i] = commentList[framePtr+i];
18984             commentList[framePtr+i] = NULL;
18985         }
18986         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18987         framePtr = savedFramePtr[storedGames];
18988         gameInfo.result = savedResult[storedGames];
18989         if(gameInfo.resultDetails != NULL) {
18990             free(gameInfo.resultDetails);
18991       }
18992         gameInfo.resultDetails = savedDetails[storedGames];
18993         forwardMostMove = currentMove + nrMoves;
18994 }
18995
18996 Boolean
18997 PopTail (Boolean annotate)
18998 {
18999         if(appData.icsActive) return FALSE; // only in local mode
19000         if(!storedGames) return FALSE; // sanity
19001         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19002
19003         PopInner(annotate);
19004         if(currentMove < forwardMostMove) ForwardEvent(); else
19005         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19006
19007         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19008         return TRUE;
19009 }
19010
19011 void
19012 CleanupTail ()
19013 {       // remove all shelved variations
19014         int i;
19015         for(i=0; i<storedGames; i++) {
19016             if(savedDetails[i])
19017                 free(savedDetails[i]);
19018             savedDetails[i] = NULL;
19019         }
19020         for(i=framePtr; i<MAX_MOVES; i++) {
19021                 if(commentList[i]) free(commentList[i]);
19022                 commentList[i] = NULL;
19023         }
19024         framePtr = MAX_MOVES-1;
19025         storedGames = 0;
19026 }
19027
19028 void
19029 LoadVariation (int index, char *text)
19030 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19031         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19032         int level = 0, move;
19033
19034         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19035         // first find outermost bracketing variation
19036         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19037             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19038                 if(*p == '{') wait = '}'; else
19039                 if(*p == '[') wait = ']'; else
19040                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19041                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19042             }
19043             if(*p == wait) wait = NULLCHAR; // closing ]} found
19044             p++;
19045         }
19046         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19047         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19048         end[1] = NULLCHAR; // clip off comment beyond variation
19049         ToNrEvent(currentMove-1);
19050         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19051         // kludge: use ParsePV() to append variation to game
19052         move = currentMove;
19053         ParsePV(start, TRUE, TRUE);
19054         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19055         ClearPremoveHighlights();
19056         CommentPopDown();
19057         ToNrEvent(currentMove+1);
19058 }
19059
19060 int transparency[2];
19061
19062 void
19063 LoadTheme ()
19064 {
19065     char *p, *q, buf[MSG_SIZ];
19066     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19067         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
19068         ParseArgsFromString(buf);
19069         ActivateTheme(TRUE); // also redo colors
19070         return;
19071     }
19072     p = nickName;
19073     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19074     {
19075         int len;
19076         q = appData.themeNames;
19077         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
19078       if(appData.useBitmaps) {
19079         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
19080                 appData.liteBackTextureFile, appData.darkBackTextureFile,
19081                 appData.liteBackTextureMode,
19082                 appData.darkBackTextureMode );
19083       } else {
19084         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false");
19085       }
19086       if(!appData.useBitmaps || transparency[0]) {
19087         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19088       }
19089       if(!appData.useBitmaps || transparency[1]) {
19090         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19091       }
19092       if(appData.useBorder) {
19093         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19094                 appData.border);
19095       } else {
19096         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19097       }
19098       if(appData.useFont) {
19099         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19100                 appData.renderPiecesWithFont,
19101                 appData.fontToPieceTable,
19102                 Col2Text(9),    // appData.fontBackColorWhite
19103                 Col2Text(10) ); // appData.fontForeColorBlack
19104       } else {
19105         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false");
19106         if(appData.pieceDirectory[0]) {
19107           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -pid \"%s\"", appData.pieceDirectory);
19108           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19109             snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19110         }
19111         if(!appData.pieceDirectory[0] || !appData.trueColors)
19112           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19113                 Col2Text(0),   // whitePieceColor
19114                 Col2Text(1) ); // blackPieceColor
19115       }
19116       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19117                 Col2Text(4),   // highlightSquareColor
19118                 Col2Text(5) ); // premoveHighlightColor
19119         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19120         if(insert != q) insert[-1] = NULLCHAR;
19121         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19122         if(q)   free(q);
19123     }
19124     ActivateTheme(FALSE);
19125 }