Let EditPosition double-click on piece promote it
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298
299 /* States for ics_getting_history */
300 #define H_FALSE 0
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
306
307 /* whosays values for GameEnds */
308 #define GE_ICS 0
309 #define GE_ENGINE 1
310 #define GE_PLAYER 2
311 #define GE_FILE 3
312 #define GE_XBOARD 4
313 #define GE_ENGINE1 5
314 #define GE_ENGINE2 6
315
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
318
319 /* Different types of move when calling RegisterMove */
320 #define CMAIL_MOVE   0
321 #define CMAIL_RESIGN 1
322 #define CMAIL_DRAW   2
323 #define CMAIL_ACCEPT 3
324
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
329
330 /* Telnet protocol constants */
331 #define TN_WILL 0373
332 #define TN_WONT 0374
333 #define TN_DO   0375
334 #define TN_DONT 0376
335 #define TN_IAC  0377
336 #define TN_ECHO 0001
337 #define TN_SGA  0003
338 #define TN_PORT 23
339
340 char*
341 safeStrCpy (char *dst, const char *src, size_t count)
342 { // [HGM] made safe
343   int i;
344   assert( dst != NULL );
345   assert( src != NULL );
346   assert( count > 0 );
347
348   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349   if(  i == count && dst[count-1] != NULLCHAR)
350     {
351       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352       if(appData.debugMode)
353         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
354     }
355
356   return dst;
357 }
358
359 /* Some compiler can't cast u64 to double
360  * This function do the job for us:
361
362  * We use the highest bit for cast, this only
363  * works if the highest bit is not
364  * in use (This should not happen)
365  *
366  * We used this for all compiler
367  */
368 double
369 u64ToDouble (u64 value)
370 {
371   double r;
372   u64 tmp = value & u64Const(0x7fffffffffffffff);
373   r = (double)(s64)tmp;
374   if (value & u64Const(0x8000000000000000))
375        r +=  9.2233720368547758080e18; /* 2^63 */
376  return r;
377 }
378
379 /* Fake up flags for now, as we aren't keeping track of castling
380    availability yet. [HGM] Change of logic: the flag now only
381    indicates the type of castlings allowed by the rule of the game.
382    The actual rights themselves are maintained in the array
383    castlingRights, as part of the game history, and are not probed
384    by this function.
385  */
386 int
387 PosFlags (index)
388 {
389   int flags = F_ALL_CASTLE_OK;
390   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391   switch (gameInfo.variant) {
392   case VariantSuicide:
393     flags &= ~F_ALL_CASTLE_OK;
394   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395     flags |= F_IGNORE_CHECK;
396   case VariantLosers:
397     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
398     break;
399   case VariantAtomic:
400     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401     break;
402   case VariantKriegspiel:
403     flags |= F_KRIEGSPIEL_CAPTURE;
404     break;
405   case VariantCapaRandom:
406   case VariantFischeRandom:
407     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408   case VariantNoCastle:
409   case VariantShatranj:
410   case VariantCourier:
411   case VariantMakruk:
412   case VariantASEAN:
413   case VariantGrand:
414     flags &= ~F_ALL_CASTLE_OK;
415     break;
416   default:
417     break;
418   }
419   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
420   return flags;
421 }
422
423 FILE *gameFileFP, *debugFP, *serverFP;
424 char *currentDebugFile; // [HGM] debug split: to remember name
425
426 /*
427     [AS] Note: sometimes, the sscanf() function is used to parse the input
428     into a fixed-size buffer. Because of this, we must be prepared to
429     receive strings as long as the size of the input buffer, which is currently
430     set to 4K for Windows and 8K for the rest.
431     So, we must either allocate sufficiently large buffers here, or
432     reduce the size of the input buffer in the input reading part.
433 */
434
435 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
436 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
437 char thinkOutput1[MSG_SIZ*10];
438
439 ChessProgramState first, second, pairing;
440
441 /* premove variables */
442 int premoveToX = 0;
443 int premoveToY = 0;
444 int premoveFromX = 0;
445 int premoveFromY = 0;
446 int premovePromoChar = 0;
447 int gotPremove = 0;
448 Boolean alarmSounded;
449 /* end premove variables */
450
451 char *ics_prefix = "$";
452 enum ICS_TYPE ics_type = ICS_GENERIC;
453
454 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
455 int pauseExamForwardMostMove = 0;
456 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
457 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
458 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
459 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
460 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
461 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
462 int whiteFlag = FALSE, blackFlag = FALSE;
463 int userOfferedDraw = FALSE;
464 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
465 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
466 int cmailMoveType[CMAIL_MAX_GAMES];
467 long ics_clock_paused = 0;
468 ProcRef icsPR = NoProc, cmailPR = NoProc;
469 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
470 GameMode gameMode = BeginningOfGame;
471 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
472 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
473 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
474 int hiddenThinkOutputState = 0; /* [AS] */
475 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
476 int adjudicateLossPlies = 6;
477 char white_holding[64], black_holding[64];
478 TimeMark lastNodeCountTime;
479 long lastNodeCount=0;
480 int shiftKey, controlKey; // [HGM] set by mouse handler
481
482 int have_sent_ICS_logon = 0;
483 int movesPerSession;
484 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
485 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
486 Boolean adjustedClock;
487 long timeControl_2; /* [AS] Allow separate time controls */
488 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
489 long timeRemaining[2][MAX_MOVES];
490 int matchGame = 0, nextGame = 0, roundNr = 0;
491 Boolean waitingForGame = FALSE, startingEngine = FALSE;
492 TimeMark programStartTime, pauseStart;
493 char ics_handle[MSG_SIZ];
494 int have_set_title = 0;
495
496 /* animateTraining preserves the state of appData.animate
497  * when Training mode is activated. This allows the
498  * response to be animated when appData.animate == TRUE and
499  * appData.animateDragging == TRUE.
500  */
501 Boolean animateTraining;
502
503 GameInfo gameInfo;
504
505 AppData appData;
506
507 Board boards[MAX_MOVES];
508 /* [HGM] Following 7 needed for accurate legality tests: */
509 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
510 signed char  initialRights[BOARD_FILES];
511 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
512 int   initialRulePlies, FENrulePlies;
513 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
514 int loadFlag = 0;
515 Boolean shuffleOpenings;
516 int mute; // mute all sounds
517
518 // [HGM] vari: next 12 to save and restore variations
519 #define MAX_VARIATIONS 10
520 int framePtr = MAX_MOVES-1; // points to free stack entry
521 int storedGames = 0;
522 int savedFirst[MAX_VARIATIONS];
523 int savedLast[MAX_VARIATIONS];
524 int savedFramePtr[MAX_VARIATIONS];
525 char *savedDetails[MAX_VARIATIONS];
526 ChessMove savedResult[MAX_VARIATIONS];
527
528 void PushTail P((int firstMove, int lastMove));
529 Boolean PopTail P((Boolean annotate));
530 void PushInner P((int firstMove, int lastMove));
531 void PopInner P((Boolean annotate));
532 void CleanupTail P((void));
533
534 ChessSquare  FIDEArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
538         BlackKing, BlackBishop, BlackKnight, BlackRook }
539 };
540
541 ChessSquare twoKingsArray[2][BOARD_FILES] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545         BlackKing, BlackKing, BlackKnight, BlackRook }
546 };
547
548 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
550         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
551     { BlackRook, BlackMan, BlackBishop, BlackQueen,
552         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
553 };
554
555 ChessSquare SpartanArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
557         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
559         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
560 };
561
562 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
566         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
567 };
568
569 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
571         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
573         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackMan, BlackFerz,
580         BlackKing, BlackMan, BlackKnight, BlackRook }
581 };
582
583 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
585         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackMan, BlackFerz,
587         BlackKing, BlackMan, BlackKnight, BlackRook }
588 };
589
590 ChessSquare  lionArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
592         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
593     { BlackRook, BlackLion, BlackBishop, BlackQueen,
594         BlackKing, BlackBishop, BlackKnight, BlackRook }
595 };
596
597
598 #if (BOARD_FILES>=10)
599 ChessSquare ShogiArray[2][BOARD_FILES] = {
600     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
601         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
602     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
603         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
604 };
605
606 ChessSquare XiangqiArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
608         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
609     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
610         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
611 };
612
613 ChessSquare CapablancaArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
615         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
617         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
618 };
619
620 ChessSquare GreatArray[2][BOARD_FILES] = {
621     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
622         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
623     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
624         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
625 };
626
627 ChessSquare JanusArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
629         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
630     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
631         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
632 };
633
634 ChessSquare GrandArray[2][BOARD_FILES] = {
635     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
636         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
637     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
638         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
639 };
640
641 ChessSquare ChuChessArray[2][BOARD_FILES] = {
642     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
643         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
644     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
645         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
646 };
647
648 #ifdef GOTHIC
649 ChessSquare GothicArray[2][BOARD_FILES] = {
650     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
651         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
652     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
653         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
654 };
655 #else // !GOTHIC
656 #define GothicArray CapablancaArray
657 #endif // !GOTHIC
658
659 #ifdef FALCON
660 ChessSquare FalconArray[2][BOARD_FILES] = {
661     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
662         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
663     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
664         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
665 };
666 #else // !FALCON
667 #define FalconArray CapablancaArray
668 #endif // !FALCON
669
670 #else // !(BOARD_FILES>=10)
671 #define XiangqiPosition FIDEArray
672 #define CapablancaArray FIDEArray
673 #define GothicArray FIDEArray
674 #define GreatArray FIDEArray
675 #endif // !(BOARD_FILES>=10)
676
677 #if (BOARD_FILES>=12)
678 ChessSquare CourierArray[2][BOARD_FILES] = {
679     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
680         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
681     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
682         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
683 };
684 ChessSquare ChuArray[6][BOARD_FILES] = {
685     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
686       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
687     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
688       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
689     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
690       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
691     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
692       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
693     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
694       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
695     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
696       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
697 };
698 #else // !(BOARD_FILES>=12)
699 #define CourierArray CapablancaArray
700 #define ChuArray CapablancaArray
701 #endif // !(BOARD_FILES>=12)
702
703
704 Board initialPosition;
705
706
707 /* Convert str to a rating. Checks for special cases of "----",
708
709    "++++", etc. Also strips ()'s */
710 int
711 string_to_rating (char *str)
712 {
713   while(*str && !isdigit(*str)) ++str;
714   if (!*str)
715     return 0;   /* One of the special "no rating" cases */
716   else
717     return atoi(str);
718 }
719
720 void
721 ClearProgramStats ()
722 {
723     /* Init programStats */
724     programStats.movelist[0] = 0;
725     programStats.depth = 0;
726     programStats.nr_moves = 0;
727     programStats.moves_left = 0;
728     programStats.nodes = 0;
729     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
730     programStats.score = 0;
731     programStats.got_only_move = 0;
732     programStats.got_fail = 0;
733     programStats.line_is_book = 0;
734 }
735
736 void
737 CommonEngineInit ()
738 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
739     if (appData.firstPlaysBlack) {
740         first.twoMachinesColor = "black\n";
741         second.twoMachinesColor = "white\n";
742     } else {
743         first.twoMachinesColor = "white\n";
744         second.twoMachinesColor = "black\n";
745     }
746
747     first.other = &second;
748     second.other = &first;
749
750     { float norm = 1;
751         if(appData.timeOddsMode) {
752             norm = appData.timeOdds[0];
753             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
754         }
755         first.timeOdds  = appData.timeOdds[0]/norm;
756         second.timeOdds = appData.timeOdds[1]/norm;
757     }
758
759     if(programVersion) free(programVersion);
760     if (appData.noChessProgram) {
761         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
762         sprintf(programVersion, "%s", PACKAGE_STRING);
763     } else {
764       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
765       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
766       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
767     }
768 }
769
770 void
771 UnloadEngine (ChessProgramState *cps)
772 {
773         /* Kill off first chess program */
774         if (cps->isr != NULL)
775           RemoveInputSource(cps->isr);
776         cps->isr = NULL;
777
778         if (cps->pr != NoProc) {
779             ExitAnalyzeMode();
780             DoSleep( appData.delayBeforeQuit );
781             SendToProgram("quit\n", cps);
782             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
783         }
784         cps->pr = NoProc;
785         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
786 }
787
788 void
789 ClearOptions (ChessProgramState *cps)
790 {
791     int i;
792     cps->nrOptions = cps->comboCnt = 0;
793     for(i=0; i<MAX_OPTIONS; i++) {
794         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
795         cps->option[i].textValue = 0;
796     }
797 }
798
799 char *engineNames[] = {
800   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
801      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
802 N_("first"),
803   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
804      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
805 N_("second")
806 };
807
808 void
809 InitEngine (ChessProgramState *cps, int n)
810 {   // [HGM] all engine initialiation put in a function that does one engine
811
812     ClearOptions(cps);
813
814     cps->which = engineNames[n];
815     cps->maybeThinking = FALSE;
816     cps->pr = NoProc;
817     cps->isr = NULL;
818     cps->sendTime = 2;
819     cps->sendDrawOffers = 1;
820
821     cps->program = appData.chessProgram[n];
822     cps->host = appData.host[n];
823     cps->dir = appData.directory[n];
824     cps->initString = appData.engInitString[n];
825     cps->computerString = appData.computerString[n];
826     cps->useSigint  = TRUE;
827     cps->useSigterm = TRUE;
828     cps->reuse = appData.reuse[n];
829     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
830     cps->useSetboard = FALSE;
831     cps->useSAN = FALSE;
832     cps->usePing = FALSE;
833     cps->lastPing = 0;
834     cps->lastPong = 0;
835     cps->usePlayother = FALSE;
836     cps->useColors = TRUE;
837     cps->useUsermove = FALSE;
838     cps->sendICS = FALSE;
839     cps->sendName = appData.icsActive;
840     cps->sdKludge = FALSE;
841     cps->stKludge = FALSE;
842     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
843     TidyProgramName(cps->program, cps->host, cps->tidy);
844     cps->matchWins = 0;
845     ASSIGN(cps->variants, appData.variant);
846     cps->analysisSupport = 2; /* detect */
847     cps->analyzing = FALSE;
848     cps->initDone = FALSE;
849     cps->reload = FALSE;
850     cps->pseudo = appData.pseudo[n];
851
852     /* New features added by Tord: */
853     cps->useFEN960 = FALSE;
854     cps->useOOCastle = TRUE;
855     /* End of new features added by Tord. */
856     cps->fenOverride  = appData.fenOverride[n];
857
858     /* [HGM] time odds: set factor for each machine */
859     cps->timeOdds  = appData.timeOdds[n];
860
861     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
862     cps->accumulateTC = appData.accumulateTC[n];
863     cps->maxNrOfSessions = 1;
864
865     /* [HGM] debug */
866     cps->debug = FALSE;
867
868     cps->drawDepth = appData.drawDepth[n];
869     cps->supportsNPS = UNKNOWN;
870     cps->memSize = FALSE;
871     cps->maxCores = FALSE;
872     ASSIGN(cps->egtFormats, "");
873
874     /* [HGM] options */
875     cps->optionSettings  = appData.engOptions[n];
876
877     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
878     cps->isUCI = appData.isUCI[n]; /* [AS] */
879     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
880     cps->highlight = 0;
881
882     if (appData.protocolVersion[n] > PROTOVER
883         || appData.protocolVersion[n] < 1)
884       {
885         char buf[MSG_SIZ];
886         int len;
887
888         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
889                        appData.protocolVersion[n]);
890         if( (len >= MSG_SIZ) && appData.debugMode )
891           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
892
893         DisplayFatalError(buf, 0, 2);
894       }
895     else
896       {
897         cps->protocolVersion = appData.protocolVersion[n];
898       }
899
900     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
901     ParseFeatures(appData.featureDefaults, cps);
902 }
903
904 ChessProgramState *savCps;
905
906 GameMode oldMode;
907
908 void
909 LoadEngine ()
910 {
911     int i;
912     if(WaitForEngine(savCps, LoadEngine)) return;
913     CommonEngineInit(); // recalculate time odds
914     if(gameInfo.variant != StringToVariant(appData.variant)) {
915         // we changed variant when loading the engine; this forces us to reset
916         Reset(TRUE, savCps != &first);
917         oldMode = BeginningOfGame; // to prevent restoring old mode
918     }
919     InitChessProgram(savCps, FALSE);
920     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
921     DisplayMessage("", "");
922     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
923     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
924     ThawUI();
925     SetGNUMode();
926     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
927 }
928
929 void
930 ReplaceEngine (ChessProgramState *cps, int n)
931 {
932     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
933     keepInfo = 1;
934     if(oldMode != BeginningOfGame) EditGameEvent();
935     keepInfo = 0;
936     UnloadEngine(cps);
937     appData.noChessProgram = FALSE;
938     appData.clockMode = TRUE;
939     InitEngine(cps, n);
940     UpdateLogos(TRUE);
941     if(n) return; // only startup first engine immediately; second can wait
942     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
943     LoadEngine();
944 }
945
946 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
947 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
948
949 static char resetOptions[] =
950         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
951         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
952         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
953         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
954
955 void
956 FloatToFront(char **list, char *engineLine)
957 {
958     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
959     int i=0;
960     if(appData.recentEngines <= 0) return;
961     TidyProgramName(engineLine, "localhost", tidy+1);
962     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
963     strncpy(buf+1, *list, MSG_SIZ-50);
964     if(p = strstr(buf, tidy)) { // tidy name appears in list
965         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
966         while(*p++ = *++q); // squeeze out
967     }
968     strcat(tidy, buf+1); // put list behind tidy name
969     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
970     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
971     ASSIGN(*list, tidy+1);
972 }
973
974 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
975
976 void
977 Load (ChessProgramState *cps, int i)
978 {
979     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
980     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
981         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
982         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
983         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
984         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
985         appData.firstProtocolVersion = PROTOVER;
986         ParseArgsFromString(buf);
987         SwapEngines(i);
988         ReplaceEngine(cps, i);
989         FloatToFront(&appData.recentEngineList, engineLine);
990         return;
991     }
992     p = engineName;
993     while(q = strchr(p, SLASH)) p = q+1;
994     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
995     if(engineDir[0] != NULLCHAR) {
996         ASSIGN(appData.directory[i], engineDir); p = engineName;
997     } else if(p != engineName) { // derive directory from engine path, when not given
998         p[-1] = 0;
999         ASSIGN(appData.directory[i], engineName);
1000         p[-1] = SLASH;
1001         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1002     } else { ASSIGN(appData.directory[i], "."); }
1003     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1004     if(params[0]) {
1005         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1006         snprintf(command, MSG_SIZ, "%s %s", p, params);
1007         p = command;
1008     }
1009     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1010     ASSIGN(appData.chessProgram[i], p);
1011     appData.isUCI[i] = isUCI;
1012     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1013     appData.hasOwnBookUCI[i] = hasBook;
1014     if(!nickName[0]) useNick = FALSE;
1015     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1016     if(addToList) {
1017         int len;
1018         char quote;
1019         q = firstChessProgramNames;
1020         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1021         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1022         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1023                         quote, p, quote, appData.directory[i],
1024                         useNick ? " -fn \"" : "",
1025                         useNick ? nickName : "",
1026                         useNick ? "\"" : "",
1027                         v1 ? " -firstProtocolVersion 1" : "",
1028                         hasBook ? "" : " -fNoOwnBookUCI",
1029                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1030                         storeVariant ? " -variant " : "",
1031                         storeVariant ? VariantName(gameInfo.variant) : "");
1032         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1033         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1034         if(insert != q) insert[-1] = NULLCHAR;
1035         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1036         if(q)   free(q);
1037         FloatToFront(&appData.recentEngineList, buf);
1038     }
1039     ReplaceEngine(cps, i);
1040 }
1041
1042 void
1043 InitTimeControls ()
1044 {
1045     int matched, min, sec;
1046     /*
1047      * Parse timeControl resource
1048      */
1049     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1050                           appData.movesPerSession)) {
1051         char buf[MSG_SIZ];
1052         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1053         DisplayFatalError(buf, 0, 2);
1054     }
1055
1056     /*
1057      * Parse searchTime resource
1058      */
1059     if (*appData.searchTime != NULLCHAR) {
1060         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1061         if (matched == 1) {
1062             searchTime = min * 60;
1063         } else if (matched == 2) {
1064             searchTime = min * 60 + sec;
1065         } else {
1066             char buf[MSG_SIZ];
1067             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1068             DisplayFatalError(buf, 0, 2);
1069         }
1070     }
1071 }
1072
1073 void
1074 InitBackEnd1 ()
1075 {
1076
1077     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1078     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1079
1080     GetTimeMark(&programStartTime);
1081     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1082     appData.seedBase = random() + (random()<<15);
1083     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1084
1085     ClearProgramStats();
1086     programStats.ok_to_send = 1;
1087     programStats.seen_stat = 0;
1088
1089     /*
1090      * Initialize game list
1091      */
1092     ListNew(&gameList);
1093
1094
1095     /*
1096      * Internet chess server status
1097      */
1098     if (appData.icsActive) {
1099         appData.matchMode = FALSE;
1100         appData.matchGames = 0;
1101 #if ZIPPY
1102         appData.noChessProgram = !appData.zippyPlay;
1103 #else
1104         appData.zippyPlay = FALSE;
1105         appData.zippyTalk = FALSE;
1106         appData.noChessProgram = TRUE;
1107 #endif
1108         if (*appData.icsHelper != NULLCHAR) {
1109             appData.useTelnet = TRUE;
1110             appData.telnetProgram = appData.icsHelper;
1111         }
1112     } else {
1113         appData.zippyTalk = appData.zippyPlay = FALSE;
1114     }
1115
1116     /* [AS] Initialize pv info list [HGM] and game state */
1117     {
1118         int i, j;
1119
1120         for( i=0; i<=framePtr; i++ ) {
1121             pvInfoList[i].depth = -1;
1122             boards[i][EP_STATUS] = EP_NONE;
1123             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1124         }
1125     }
1126
1127     InitTimeControls();
1128
1129     /* [AS] Adjudication threshold */
1130     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1131
1132     InitEngine(&first, 0);
1133     InitEngine(&second, 1);
1134     CommonEngineInit();
1135
1136     pairing.which = "pairing"; // pairing engine
1137     pairing.pr = NoProc;
1138     pairing.isr = NULL;
1139     pairing.program = appData.pairingEngine;
1140     pairing.host = "localhost";
1141     pairing.dir = ".";
1142
1143     if (appData.icsActive) {
1144         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1145     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1146         appData.clockMode = FALSE;
1147         first.sendTime = second.sendTime = 0;
1148     }
1149
1150 #if ZIPPY
1151     /* Override some settings from environment variables, for backward
1152        compatibility.  Unfortunately it's not feasible to have the env
1153        vars just set defaults, at least in xboard.  Ugh.
1154     */
1155     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1156       ZippyInit();
1157     }
1158 #endif
1159
1160     if (!appData.icsActive) {
1161       char buf[MSG_SIZ];
1162       int len;
1163
1164       /* Check for variants that are supported only in ICS mode,
1165          or not at all.  Some that are accepted here nevertheless
1166          have bugs; see comments below.
1167       */
1168       VariantClass variant = StringToVariant(appData.variant);
1169       switch (variant) {
1170       case VariantBughouse:     /* need four players and two boards */
1171       case VariantKriegspiel:   /* need to hide pieces and move details */
1172         /* case VariantFischeRandom: (Fabien: moved below) */
1173         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1174         if( (len >= MSG_SIZ) && appData.debugMode )
1175           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1176
1177         DisplayFatalError(buf, 0, 2);
1178         return;
1179
1180       case VariantUnknown:
1181       case VariantLoadable:
1182       case Variant29:
1183       case Variant30:
1184       case Variant31:
1185       case Variant32:
1186       case Variant33:
1187       case Variant34:
1188       case Variant35:
1189       case Variant36:
1190       default:
1191         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1192         if( (len >= MSG_SIZ) && appData.debugMode )
1193           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1194
1195         DisplayFatalError(buf, 0, 2);
1196         return;
1197
1198       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1199       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1200       case VariantGothic:     /* [HGM] should work */
1201       case VariantCapablanca: /* [HGM] should work */
1202       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1203       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1204       case VariantChu:        /* [HGM] experimental */
1205       case VariantKnightmate: /* [HGM] should work */
1206       case VariantCylinder:   /* [HGM] untested */
1207       case VariantFalcon:     /* [HGM] untested */
1208       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1209                                  offboard interposition not understood */
1210       case VariantNormal:     /* definitely works! */
1211       case VariantWildCastle: /* pieces not automatically shuffled */
1212       case VariantNoCastle:   /* pieces not automatically shuffled */
1213       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1214       case VariantLosers:     /* should work except for win condition,
1215                                  and doesn't know captures are mandatory */
1216       case VariantSuicide:    /* should work except for win condition,
1217                                  and doesn't know captures are mandatory */
1218       case VariantGiveaway:   /* should work except for win condition,
1219                                  and doesn't know captures are mandatory */
1220       case VariantTwoKings:   /* should work */
1221       case VariantAtomic:     /* should work except for win condition */
1222       case Variant3Check:     /* should work except for win condition */
1223       case VariantShatranj:   /* should work except for all win conditions */
1224       case VariantMakruk:     /* should work except for draw countdown */
1225       case VariantASEAN :     /* should work except for draw countdown */
1226       case VariantBerolina:   /* might work if TestLegality is off */
1227       case VariantCapaRandom: /* should work */
1228       case VariantJanus:      /* should work */
1229       case VariantSuper:      /* experimental */
1230       case VariantGreat:      /* experimental, requires legality testing to be off */
1231       case VariantSChess:     /* S-Chess, should work */
1232       case VariantGrand:      /* should work */
1233       case VariantSpartan:    /* should work */
1234       case VariantLion:       /* should work */
1235       case VariantChuChess:   /* should work */
1236         break;
1237       }
1238     }
1239
1240 }
1241
1242 int
1243 NextIntegerFromString (char ** str, long * value)
1244 {
1245     int result = -1;
1246     char * s = *str;
1247
1248     while( *s == ' ' || *s == '\t' ) {
1249         s++;
1250     }
1251
1252     *value = 0;
1253
1254     if( *s >= '0' && *s <= '9' ) {
1255         while( *s >= '0' && *s <= '9' ) {
1256             *value = *value * 10 + (*s - '0');
1257             s++;
1258         }
1259
1260         result = 0;
1261     }
1262
1263     *str = s;
1264
1265     return result;
1266 }
1267
1268 int
1269 NextTimeControlFromString (char ** str, long * value)
1270 {
1271     long temp;
1272     int result = NextIntegerFromString( str, &temp );
1273
1274     if( result == 0 ) {
1275         *value = temp * 60; /* Minutes */
1276         if( **str == ':' ) {
1277             (*str)++;
1278             result = NextIntegerFromString( str, &temp );
1279             *value += temp; /* Seconds */
1280         }
1281     }
1282
1283     return result;
1284 }
1285
1286 int
1287 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1288 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1289     int result = -1, type = 0; long temp, temp2;
1290
1291     if(**str != ':') return -1; // old params remain in force!
1292     (*str)++;
1293     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1294     if( NextIntegerFromString( str, &temp ) ) return -1;
1295     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1296
1297     if(**str != '/') {
1298         /* time only: incremental or sudden-death time control */
1299         if(**str == '+') { /* increment follows; read it */
1300             (*str)++;
1301             if(**str == '!') type = *(*str)++; // Bronstein TC
1302             if(result = NextIntegerFromString( str, &temp2)) return -1;
1303             *inc = temp2 * 1000;
1304             if(**str == '.') { // read fraction of increment
1305                 char *start = ++(*str);
1306                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1307                 temp2 *= 1000;
1308                 while(start++ < *str) temp2 /= 10;
1309                 *inc += temp2;
1310             }
1311         } else *inc = 0;
1312         *moves = 0; *tc = temp * 1000; *incType = type;
1313         return 0;
1314     }
1315
1316     (*str)++; /* classical time control */
1317     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1318
1319     if(result == 0) {
1320         *moves = temp;
1321         *tc    = temp2 * 1000;
1322         *inc   = 0;
1323         *incType = type;
1324     }
1325     return result;
1326 }
1327
1328 int
1329 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1330 {   /* [HGM] get time to add from the multi-session time-control string */
1331     int incType, moves=1; /* kludge to force reading of first session */
1332     long time, increment;
1333     char *s = tcString;
1334
1335     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1336     do {
1337         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1338         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1339         if(movenr == -1) return time;    /* last move before new session     */
1340         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1341         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1342         if(!moves) return increment;     /* current session is incremental   */
1343         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1344     } while(movenr >= -1);               /* try again for next session       */
1345
1346     return 0; // no new time quota on this move
1347 }
1348
1349 int
1350 ParseTimeControl (char *tc, float ti, int mps)
1351 {
1352   long tc1;
1353   long tc2;
1354   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1355   int min, sec=0;
1356
1357   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1358   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1359       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1360   if(ti > 0) {
1361
1362     if(mps)
1363       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1364     else
1365       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1366   } else {
1367     if(mps)
1368       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1369     else
1370       snprintf(buf, MSG_SIZ, ":%s", mytc);
1371   }
1372   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1373
1374   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1375     return FALSE;
1376   }
1377
1378   if( *tc == '/' ) {
1379     /* Parse second time control */
1380     tc++;
1381
1382     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1383       return FALSE;
1384     }
1385
1386     if( tc2 == 0 ) {
1387       return FALSE;
1388     }
1389
1390     timeControl_2 = tc2 * 1000;
1391   }
1392   else {
1393     timeControl_2 = 0;
1394   }
1395
1396   if( tc1 == 0 ) {
1397     return FALSE;
1398   }
1399
1400   timeControl = tc1 * 1000;
1401
1402   if (ti >= 0) {
1403     timeIncrement = ti * 1000;  /* convert to ms */
1404     movesPerSession = 0;
1405   } else {
1406     timeIncrement = 0;
1407     movesPerSession = mps;
1408   }
1409   return TRUE;
1410 }
1411
1412 void
1413 InitBackEnd2 ()
1414 {
1415     if (appData.debugMode) {
1416 #    ifdef __GIT_VERSION
1417       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1418 #    else
1419       fprintf(debugFP, "Version: %s\n", programVersion);
1420 #    endif
1421     }
1422     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1423
1424     set_cont_sequence(appData.wrapContSeq);
1425     if (appData.matchGames > 0) {
1426         appData.matchMode = TRUE;
1427     } else if (appData.matchMode) {
1428         appData.matchGames = 1;
1429     }
1430     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1431         appData.matchGames = appData.sameColorGames;
1432     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1433         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1434         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1435     }
1436     Reset(TRUE, FALSE);
1437     if (appData.noChessProgram || first.protocolVersion == 1) {
1438       InitBackEnd3();
1439     } else {
1440       /* kludge: allow timeout for initial "feature" commands */
1441       FreezeUI();
1442       DisplayMessage("", _("Starting chess program"));
1443       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1444     }
1445 }
1446
1447 int
1448 CalculateIndex (int index, int gameNr)
1449 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1450     int res;
1451     if(index > 0) return index; // fixed nmber
1452     if(index == 0) return 1;
1453     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1454     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1455     return res;
1456 }
1457
1458 int
1459 LoadGameOrPosition (int gameNr)
1460 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1461     if (*appData.loadGameFile != NULLCHAR) {
1462         if (!LoadGameFromFile(appData.loadGameFile,
1463                 CalculateIndex(appData.loadGameIndex, gameNr),
1464                               appData.loadGameFile, FALSE)) {
1465             DisplayFatalError(_("Bad game file"), 0, 1);
1466             return 0;
1467         }
1468     } else if (*appData.loadPositionFile != NULLCHAR) {
1469         if (!LoadPositionFromFile(appData.loadPositionFile,
1470                 CalculateIndex(appData.loadPositionIndex, gameNr),
1471                                   appData.loadPositionFile)) {
1472             DisplayFatalError(_("Bad position file"), 0, 1);
1473             return 0;
1474         }
1475     }
1476     return 1;
1477 }
1478
1479 void
1480 ReserveGame (int gameNr, char resChar)
1481 {
1482     FILE *tf = fopen(appData.tourneyFile, "r+");
1483     char *p, *q, c, buf[MSG_SIZ];
1484     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1485     safeStrCpy(buf, lastMsg, MSG_SIZ);
1486     DisplayMessage(_("Pick new game"), "");
1487     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1488     ParseArgsFromFile(tf);
1489     p = q = appData.results;
1490     if(appData.debugMode) {
1491       char *r = appData.participants;
1492       fprintf(debugFP, "results = '%s'\n", p);
1493       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1494       fprintf(debugFP, "\n");
1495     }
1496     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1497     nextGame = q - p;
1498     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1499     safeStrCpy(q, p, strlen(p) + 2);
1500     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1501     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1502     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1503         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1504         q[nextGame] = '*';
1505     }
1506     fseek(tf, -(strlen(p)+4), SEEK_END);
1507     c = fgetc(tf);
1508     if(c != '"') // depending on DOS or Unix line endings we can be one off
1509          fseek(tf, -(strlen(p)+2), SEEK_END);
1510     else fseek(tf, -(strlen(p)+3), SEEK_END);
1511     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1512     DisplayMessage(buf, "");
1513     free(p); appData.results = q;
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1515        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1516       int round = appData.defaultMatchGames * appData.tourneyType;
1517       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1518          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1519         UnloadEngine(&first);  // next game belongs to other pairing;
1520         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1521     }
1522     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1523 }
1524
1525 void
1526 MatchEvent (int mode)
1527 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1528         int dummy;
1529         if(matchMode) { // already in match mode: switch it off
1530             abortMatch = TRUE;
1531             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1532             return;
1533         }
1534 //      if(gameMode != BeginningOfGame) {
1535 //          DisplayError(_("You can only start a match from the initial position."), 0);
1536 //          return;
1537 //      }
1538         abortMatch = FALSE;
1539         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1540         /* Set up machine vs. machine match */
1541         nextGame = 0;
1542         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1543         if(appData.tourneyFile[0]) {
1544             ReserveGame(-1, 0);
1545             if(nextGame > appData.matchGames) {
1546                 char buf[MSG_SIZ];
1547                 if(strchr(appData.results, '*') == NULL) {
1548                     FILE *f;
1549                     appData.tourneyCycles++;
1550                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1551                         fclose(f);
1552                         NextTourneyGame(-1, &dummy);
1553                         ReserveGame(-1, 0);
1554                         if(nextGame <= appData.matchGames) {
1555                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1556                             matchMode = mode;
1557                             ScheduleDelayedEvent(NextMatchGame, 10000);
1558                             return;
1559                         }
1560                     }
1561                 }
1562                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1563                 DisplayError(buf, 0);
1564                 appData.tourneyFile[0] = 0;
1565                 return;
1566             }
1567         } else
1568         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1569             DisplayFatalError(_("Can't have a match with no chess programs"),
1570                               0, 2);
1571             return;
1572         }
1573         matchMode = mode;
1574         matchGame = roundNr = 1;
1575         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1576         NextMatchGame();
1577 }
1578
1579 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1580
1581 void
1582 InitBackEnd3 P((void))
1583 {
1584     GameMode initialMode;
1585     char buf[MSG_SIZ];
1586     int err, len;
1587
1588     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1589        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1590         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1591        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1592        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1593         char c, *q = first.variants, *p = strchr(q, ',');
1594         if(p) *p = NULLCHAR;
1595         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1596             int w, h, s;
1597             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1598                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1599             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1600             Reset(TRUE, FALSE);         // and re-initialize
1601         }
1602         if(p) *p = ',';
1603     }
1604
1605     InitChessProgram(&first, startedFromSetupPosition);
1606
1607     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1608         free(programVersion);
1609         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1610         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1611         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1612     }
1613
1614     if (appData.icsActive) {
1615 #ifdef WIN32
1616         /* [DM] Make a console window if needed [HGM] merged ifs */
1617         ConsoleCreate();
1618 #endif
1619         err = establish();
1620         if (err != 0)
1621           {
1622             if (*appData.icsCommPort != NULLCHAR)
1623               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1624                              appData.icsCommPort);
1625             else
1626               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1627                         appData.icsHost, appData.icsPort);
1628
1629             if( (len >= MSG_SIZ) && appData.debugMode )
1630               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1631
1632             DisplayFatalError(buf, err, 1);
1633             return;
1634         }
1635         SetICSMode();
1636         telnetISR =
1637           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1638         fromUserISR =
1639           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1640         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1641             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1642     } else if (appData.noChessProgram) {
1643         SetNCPMode();
1644     } else {
1645         SetGNUMode();
1646     }
1647
1648     if (*appData.cmailGameName != NULLCHAR) {
1649         SetCmailMode();
1650         OpenLoopback(&cmailPR);
1651         cmailISR =
1652           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1653     }
1654
1655     ThawUI();
1656     DisplayMessage("", "");
1657     if (StrCaseCmp(appData.initialMode, "") == 0) {
1658       initialMode = BeginningOfGame;
1659       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1660         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1661         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1662         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1663         ModeHighlight();
1664       }
1665     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1666       initialMode = TwoMachinesPlay;
1667     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1668       initialMode = AnalyzeFile;
1669     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1670       initialMode = AnalyzeMode;
1671     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1672       initialMode = MachinePlaysWhite;
1673     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1674       initialMode = MachinePlaysBlack;
1675     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1676       initialMode = EditGame;
1677     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1678       initialMode = EditPosition;
1679     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1680       initialMode = Training;
1681     } else {
1682       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1683       if( (len >= MSG_SIZ) && appData.debugMode )
1684         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1685
1686       DisplayFatalError(buf, 0, 2);
1687       return;
1688     }
1689
1690     if (appData.matchMode) {
1691         if(appData.tourneyFile[0]) { // start tourney from command line
1692             FILE *f;
1693             if(f = fopen(appData.tourneyFile, "r")) {
1694                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1695                 fclose(f);
1696                 appData.clockMode = TRUE;
1697                 SetGNUMode();
1698             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1699         }
1700         MatchEvent(TRUE);
1701     } else if (*appData.cmailGameName != NULLCHAR) {
1702         /* Set up cmail mode */
1703         ReloadCmailMsgEvent(TRUE);
1704     } else {
1705         /* Set up other modes */
1706         if (initialMode == AnalyzeFile) {
1707           if (*appData.loadGameFile == NULLCHAR) {
1708             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1709             return;
1710           }
1711         }
1712         if (*appData.loadGameFile != NULLCHAR) {
1713             (void) LoadGameFromFile(appData.loadGameFile,
1714                                     appData.loadGameIndex,
1715                                     appData.loadGameFile, TRUE);
1716         } else if (*appData.loadPositionFile != NULLCHAR) {
1717             (void) LoadPositionFromFile(appData.loadPositionFile,
1718                                         appData.loadPositionIndex,
1719                                         appData.loadPositionFile);
1720             /* [HGM] try to make self-starting even after FEN load */
1721             /* to allow automatic setup of fairy variants with wtm */
1722             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1723                 gameMode = BeginningOfGame;
1724                 setboardSpoiledMachineBlack = 1;
1725             }
1726             /* [HGM] loadPos: make that every new game uses the setup */
1727             /* from file as long as we do not switch variant          */
1728             if(!blackPlaysFirst) {
1729                 startedFromPositionFile = TRUE;
1730                 CopyBoard(filePosition, boards[0]);
1731             }
1732         }
1733         if (initialMode == AnalyzeMode) {
1734           if (appData.noChessProgram) {
1735             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1736             return;
1737           }
1738           if (appData.icsActive) {
1739             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1740             return;
1741           }
1742           AnalyzeModeEvent();
1743         } else if (initialMode == AnalyzeFile) {
1744           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1745           ShowThinkingEvent();
1746           AnalyzeFileEvent();
1747           AnalysisPeriodicEvent(1);
1748         } else if (initialMode == MachinePlaysWhite) {
1749           if (appData.noChessProgram) {
1750             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1751                               0, 2);
1752             return;
1753           }
1754           if (appData.icsActive) {
1755             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1756                               0, 2);
1757             return;
1758           }
1759           MachineWhiteEvent();
1760         } else if (initialMode == MachinePlaysBlack) {
1761           if (appData.noChessProgram) {
1762             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1763                               0, 2);
1764             return;
1765           }
1766           if (appData.icsActive) {
1767             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1768                               0, 2);
1769             return;
1770           }
1771           MachineBlackEvent();
1772         } else if (initialMode == TwoMachinesPlay) {
1773           if (appData.noChessProgram) {
1774             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1775                               0, 2);
1776             return;
1777           }
1778           if (appData.icsActive) {
1779             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1780                               0, 2);
1781             return;
1782           }
1783           TwoMachinesEvent();
1784         } else if (initialMode == EditGame) {
1785           EditGameEvent();
1786         } else if (initialMode == EditPosition) {
1787           EditPositionEvent();
1788         } else if (initialMode == Training) {
1789           if (*appData.loadGameFile == NULLCHAR) {
1790             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1791             return;
1792           }
1793           TrainingEvent();
1794         }
1795     }
1796 }
1797
1798 void
1799 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1800 {
1801     DisplayBook(current+1);
1802
1803     MoveHistorySet( movelist, first, last, current, pvInfoList );
1804
1805     EvalGraphSet( first, last, current, pvInfoList );
1806
1807     MakeEngineOutputTitle();
1808 }
1809
1810 /*
1811  * Establish will establish a contact to a remote host.port.
1812  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1813  *  used to talk to the host.
1814  * Returns 0 if okay, error code if not.
1815  */
1816 int
1817 establish ()
1818 {
1819     char buf[MSG_SIZ];
1820
1821     if (*appData.icsCommPort != NULLCHAR) {
1822         /* Talk to the host through a serial comm port */
1823         return OpenCommPort(appData.icsCommPort, &icsPR);
1824
1825     } else if (*appData.gateway != NULLCHAR) {
1826         if (*appData.remoteShell == NULLCHAR) {
1827             /* Use the rcmd protocol to run telnet program on a gateway host */
1828             snprintf(buf, sizeof(buf), "%s %s %s",
1829                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1830             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1831
1832         } else {
1833             /* Use the rsh program to run telnet program on a gateway host */
1834             if (*appData.remoteUser == NULLCHAR) {
1835                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1836                         appData.gateway, appData.telnetProgram,
1837                         appData.icsHost, appData.icsPort);
1838             } else {
1839                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1840                         appData.remoteShell, appData.gateway,
1841                         appData.remoteUser, appData.telnetProgram,
1842                         appData.icsHost, appData.icsPort);
1843             }
1844             return StartChildProcess(buf, "", &icsPR);
1845
1846         }
1847     } else if (appData.useTelnet) {
1848         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1849
1850     } else {
1851         /* TCP socket interface differs somewhat between
1852            Unix and NT; handle details in the front end.
1853            */
1854         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1855     }
1856 }
1857
1858 void
1859 EscapeExpand (char *p, char *q)
1860 {       // [HGM] initstring: routine to shape up string arguments
1861         while(*p++ = *q++) if(p[-1] == '\\')
1862             switch(*q++) {
1863                 case 'n': p[-1] = '\n'; break;
1864                 case 'r': p[-1] = '\r'; break;
1865                 case 't': p[-1] = '\t'; break;
1866                 case '\\': p[-1] = '\\'; break;
1867                 case 0: *p = 0; return;
1868                 default: p[-1] = q[-1]; break;
1869             }
1870 }
1871
1872 void
1873 show_bytes (FILE *fp, char *buf, int count)
1874 {
1875     while (count--) {
1876         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1877             fprintf(fp, "\\%03o", *buf & 0xff);
1878         } else {
1879             putc(*buf, fp);
1880         }
1881         buf++;
1882     }
1883     fflush(fp);
1884 }
1885
1886 /* Returns an errno value */
1887 int
1888 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1889 {
1890     char buf[8192], *p, *q, *buflim;
1891     int left, newcount, outcount;
1892
1893     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1894         *appData.gateway != NULLCHAR) {
1895         if (appData.debugMode) {
1896             fprintf(debugFP, ">ICS: ");
1897             show_bytes(debugFP, message, count);
1898             fprintf(debugFP, "\n");
1899         }
1900         return OutputToProcess(pr, message, count, outError);
1901     }
1902
1903     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1904     p = message;
1905     q = buf;
1906     left = count;
1907     newcount = 0;
1908     while (left) {
1909         if (q >= buflim) {
1910             if (appData.debugMode) {
1911                 fprintf(debugFP, ">ICS: ");
1912                 show_bytes(debugFP, buf, newcount);
1913                 fprintf(debugFP, "\n");
1914             }
1915             outcount = OutputToProcess(pr, buf, newcount, outError);
1916             if (outcount < newcount) return -1; /* to be sure */
1917             q = buf;
1918             newcount = 0;
1919         }
1920         if (*p == '\n') {
1921             *q++ = '\r';
1922             newcount++;
1923         } else if (((unsigned char) *p) == TN_IAC) {
1924             *q++ = (char) TN_IAC;
1925             newcount ++;
1926         }
1927         *q++ = *p++;
1928         newcount++;
1929         left--;
1930     }
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     return count;
1939 }
1940
1941 void
1942 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1943 {
1944     int outError, outCount;
1945     static int gotEof = 0;
1946     static FILE *ini;
1947
1948     /* Pass data read from player on to ICS */
1949     if (count > 0) {
1950         gotEof = 0;
1951         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1952         if (outCount < count) {
1953             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1954         }
1955         if(have_sent_ICS_logon == 2) {
1956           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1957             fprintf(ini, "%s", message);
1958             have_sent_ICS_logon = 3;
1959           } else
1960             have_sent_ICS_logon = 1;
1961         } else if(have_sent_ICS_logon == 3) {
1962             fprintf(ini, "%s", message);
1963             fclose(ini);
1964           have_sent_ICS_logon = 1;
1965         }
1966     } else if (count < 0) {
1967         RemoveInputSource(isr);
1968         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1969     } else if (gotEof++ > 0) {
1970         RemoveInputSource(isr);
1971         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1972     }
1973 }
1974
1975 void
1976 KeepAlive ()
1977 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1978     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1979     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1980     SendToICS("date\n");
1981     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1982 }
1983
1984 /* added routine for printf style output to ics */
1985 void
1986 ics_printf (char *format, ...)
1987 {
1988     char buffer[MSG_SIZ];
1989     va_list args;
1990
1991     va_start(args, format);
1992     vsnprintf(buffer, sizeof(buffer), format, args);
1993     buffer[sizeof(buffer)-1] = '\0';
1994     SendToICS(buffer);
1995     va_end(args);
1996 }
1997
1998 void
1999 SendToICS (char *s)
2000 {
2001     int count, outCount, outError;
2002
2003     if (icsPR == NoProc) return;
2004
2005     count = strlen(s);
2006     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2007     if (outCount < count) {
2008         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2009     }
2010 }
2011
2012 /* This is used for sending logon scripts to the ICS. Sending
2013    without a delay causes problems when using timestamp on ICC
2014    (at least on my machine). */
2015 void
2016 SendToICSDelayed (char *s, long msdelay)
2017 {
2018     int count, outCount, outError;
2019
2020     if (icsPR == NoProc) return;
2021
2022     count = strlen(s);
2023     if (appData.debugMode) {
2024         fprintf(debugFP, ">ICS: ");
2025         show_bytes(debugFP, s, count);
2026         fprintf(debugFP, "\n");
2027     }
2028     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2029                                       msdelay);
2030     if (outCount < count) {
2031         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2032     }
2033 }
2034
2035
2036 /* Remove all highlighting escape sequences in s
2037    Also deletes any suffix starting with '('
2038    */
2039 char *
2040 StripHighlightAndTitle (char *s)
2041 {
2042     static char retbuf[MSG_SIZ];
2043     char *p = retbuf;
2044
2045     while (*s != NULLCHAR) {
2046         while (*s == '\033') {
2047             while (*s != NULLCHAR && !isalpha(*s)) s++;
2048             if (*s != NULLCHAR) s++;
2049         }
2050         while (*s != NULLCHAR && *s != '\033') {
2051             if (*s == '(' || *s == '[') {
2052                 *p = NULLCHAR;
2053                 return retbuf;
2054             }
2055             *p++ = *s++;
2056         }
2057     }
2058     *p = NULLCHAR;
2059     return retbuf;
2060 }
2061
2062 /* Remove all highlighting escape sequences in s */
2063 char *
2064 StripHighlight (char *s)
2065 {
2066     static char retbuf[MSG_SIZ];
2067     char *p = retbuf;
2068
2069     while (*s != NULLCHAR) {
2070         while (*s == '\033') {
2071             while (*s != NULLCHAR && !isalpha(*s)) s++;
2072             if (*s != NULLCHAR) s++;
2073         }
2074         while (*s != NULLCHAR && *s != '\033') {
2075             *p++ = *s++;
2076         }
2077     }
2078     *p = NULLCHAR;
2079     return retbuf;
2080 }
2081
2082 char engineVariant[MSG_SIZ];
2083 char *variantNames[] = VARIANT_NAMES;
2084 char *
2085 VariantName (VariantClass v)
2086 {
2087     if(v == VariantUnknown || *engineVariant) return engineVariant;
2088     return variantNames[v];
2089 }
2090
2091
2092 /* Identify a variant from the strings the chess servers use or the
2093    PGN Variant tag names we use. */
2094 VariantClass
2095 StringToVariant (char *e)
2096 {
2097     char *p;
2098     int wnum = -1;
2099     VariantClass v = VariantNormal;
2100     int i, found = FALSE;
2101     char buf[MSG_SIZ];
2102     int len;
2103
2104     if (!e) return v;
2105
2106     /* [HGM] skip over optional board-size prefixes */
2107     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2108         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2109         while( *e++ != '_');
2110     }
2111
2112     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2113         v = VariantNormal;
2114         found = TRUE;
2115     } else
2116     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2117       if (p = StrCaseStr(e, variantNames[i])) {
2118         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2119         v = (VariantClass) i;
2120         found = TRUE;
2121         break;
2122       }
2123     }
2124
2125     if (!found) {
2126       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2127           || StrCaseStr(e, "wild/fr")
2128           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2129         v = VariantFischeRandom;
2130       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2131                  (i = 1, p = StrCaseStr(e, "w"))) {
2132         p += i;
2133         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2134         if (isdigit(*p)) {
2135           wnum = atoi(p);
2136         } else {
2137           wnum = -1;
2138         }
2139         switch (wnum) {
2140         case 0: /* FICS only, actually */
2141         case 1:
2142           /* Castling legal even if K starts on d-file */
2143           v = VariantWildCastle;
2144           break;
2145         case 2:
2146         case 3:
2147         case 4:
2148           /* Castling illegal even if K & R happen to start in
2149              normal positions. */
2150           v = VariantNoCastle;
2151           break;
2152         case 5:
2153         case 7:
2154         case 8:
2155         case 10:
2156         case 11:
2157         case 12:
2158         case 13:
2159         case 14:
2160         case 15:
2161         case 18:
2162         case 19:
2163           /* Castling legal iff K & R start in normal positions */
2164           v = VariantNormal;
2165           break;
2166         case 6:
2167         case 20:
2168         case 21:
2169           /* Special wilds for position setup; unclear what to do here */
2170           v = VariantLoadable;
2171           break;
2172         case 9:
2173           /* Bizarre ICC game */
2174           v = VariantTwoKings;
2175           break;
2176         case 16:
2177           v = VariantKriegspiel;
2178           break;
2179         case 17:
2180           v = VariantLosers;
2181           break;
2182         case 22:
2183           v = VariantFischeRandom;
2184           break;
2185         case 23:
2186           v = VariantCrazyhouse;
2187           break;
2188         case 24:
2189           v = VariantBughouse;
2190           break;
2191         case 25:
2192           v = Variant3Check;
2193           break;
2194         case 26:
2195           /* Not quite the same as FICS suicide! */
2196           v = VariantGiveaway;
2197           break;
2198         case 27:
2199           v = VariantAtomic;
2200           break;
2201         case 28:
2202           v = VariantShatranj;
2203           break;
2204
2205         /* Temporary names for future ICC types.  The name *will* change in
2206            the next xboard/WinBoard release after ICC defines it. */
2207         case 29:
2208           v = Variant29;
2209           break;
2210         case 30:
2211           v = Variant30;
2212           break;
2213         case 31:
2214           v = Variant31;
2215           break;
2216         case 32:
2217           v = Variant32;
2218           break;
2219         case 33:
2220           v = Variant33;
2221           break;
2222         case 34:
2223           v = Variant34;
2224           break;
2225         case 35:
2226           v = Variant35;
2227           break;
2228         case 36:
2229           v = Variant36;
2230           break;
2231         case 37:
2232           v = VariantShogi;
2233           break;
2234         case 38:
2235           v = VariantXiangqi;
2236           break;
2237         case 39:
2238           v = VariantCourier;
2239           break;
2240         case 40:
2241           v = VariantGothic;
2242           break;
2243         case 41:
2244           v = VariantCapablanca;
2245           break;
2246         case 42:
2247           v = VariantKnightmate;
2248           break;
2249         case 43:
2250           v = VariantFairy;
2251           break;
2252         case 44:
2253           v = VariantCylinder;
2254           break;
2255         case 45:
2256           v = VariantFalcon;
2257           break;
2258         case 46:
2259           v = VariantCapaRandom;
2260           break;
2261         case 47:
2262           v = VariantBerolina;
2263           break;
2264         case 48:
2265           v = VariantJanus;
2266           break;
2267         case 49:
2268           v = VariantSuper;
2269           break;
2270         case 50:
2271           v = VariantGreat;
2272           break;
2273         case -1:
2274           /* Found "wild" or "w" in the string but no number;
2275              must assume it's normal chess. */
2276           v = VariantNormal;
2277           break;
2278         default:
2279           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2280           if( (len >= MSG_SIZ) && appData.debugMode )
2281             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2282
2283           DisplayError(buf, 0);
2284           v = VariantUnknown;
2285           break;
2286         }
2287       }
2288     }
2289     if (appData.debugMode) {
2290       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2291               e, wnum, VariantName(v));
2292     }
2293     return v;
2294 }
2295
2296 static int leftover_start = 0, leftover_len = 0;
2297 char star_match[STAR_MATCH_N][MSG_SIZ];
2298
2299 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2300    advance *index beyond it, and set leftover_start to the new value of
2301    *index; else return FALSE.  If pattern contains the character '*', it
2302    matches any sequence of characters not containing '\r', '\n', or the
2303    character following the '*' (if any), and the matched sequence(s) are
2304    copied into star_match.
2305    */
2306 int
2307 looking_at ( char *buf, int *index, char *pattern)
2308 {
2309     char *bufp = &buf[*index], *patternp = pattern;
2310     int star_count = 0;
2311     char *matchp = star_match[0];
2312
2313     for (;;) {
2314         if (*patternp == NULLCHAR) {
2315             *index = leftover_start = bufp - buf;
2316             *matchp = NULLCHAR;
2317             return TRUE;
2318         }
2319         if (*bufp == NULLCHAR) return FALSE;
2320         if (*patternp == '*') {
2321             if (*bufp == *(patternp + 1)) {
2322                 *matchp = NULLCHAR;
2323                 matchp = star_match[++star_count];
2324                 patternp += 2;
2325                 bufp++;
2326                 continue;
2327             } else if (*bufp == '\n' || *bufp == '\r') {
2328                 patternp++;
2329                 if (*patternp == NULLCHAR)
2330                   continue;
2331                 else
2332                   return FALSE;
2333             } else {
2334                 *matchp++ = *bufp++;
2335                 continue;
2336             }
2337         }
2338         if (*patternp != *bufp) return FALSE;
2339         patternp++;
2340         bufp++;
2341     }
2342 }
2343
2344 void
2345 SendToPlayer (char *data, int length)
2346 {
2347     int error, outCount;
2348     outCount = OutputToProcess(NoProc, data, length, &error);
2349     if (outCount < length) {
2350         DisplayFatalError(_("Error writing to display"), error, 1);
2351     }
2352 }
2353
2354 void
2355 PackHolding (char packed[], char *holding)
2356 {
2357     char *p = holding;
2358     char *q = packed;
2359     int runlength = 0;
2360     int curr = 9999;
2361     do {
2362         if (*p == curr) {
2363             runlength++;
2364         } else {
2365             switch (runlength) {
2366               case 0:
2367                 break;
2368               case 1:
2369                 *q++ = curr;
2370                 break;
2371               case 2:
2372                 *q++ = curr;
2373                 *q++ = curr;
2374                 break;
2375               default:
2376                 sprintf(q, "%d", runlength);
2377                 while (*q) q++;
2378                 *q++ = curr;
2379                 break;
2380             }
2381             runlength = 1;
2382             curr = *p;
2383         }
2384     } while (*p++);
2385     *q = NULLCHAR;
2386 }
2387
2388 /* Telnet protocol requests from the front end */
2389 void
2390 TelnetRequest (unsigned char ddww, unsigned char option)
2391 {
2392     unsigned char msg[3];
2393     int outCount, outError;
2394
2395     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2396
2397     if (appData.debugMode) {
2398         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2399         switch (ddww) {
2400           case TN_DO:
2401             ddwwStr = "DO";
2402             break;
2403           case TN_DONT:
2404             ddwwStr = "DONT";
2405             break;
2406           case TN_WILL:
2407             ddwwStr = "WILL";
2408             break;
2409           case TN_WONT:
2410             ddwwStr = "WONT";
2411             break;
2412           default:
2413             ddwwStr = buf1;
2414             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2415             break;
2416         }
2417         switch (option) {
2418           case TN_ECHO:
2419             optionStr = "ECHO";
2420             break;
2421           default:
2422             optionStr = buf2;
2423             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2424             break;
2425         }
2426         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2427     }
2428     msg[0] = TN_IAC;
2429     msg[1] = ddww;
2430     msg[2] = option;
2431     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2432     if (outCount < 3) {
2433         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2434     }
2435 }
2436
2437 void
2438 DoEcho ()
2439 {
2440     if (!appData.icsActive) return;
2441     TelnetRequest(TN_DO, TN_ECHO);
2442 }
2443
2444 void
2445 DontEcho ()
2446 {
2447     if (!appData.icsActive) return;
2448     TelnetRequest(TN_DONT, TN_ECHO);
2449 }
2450
2451 void
2452 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2453 {
2454     /* put the holdings sent to us by the server on the board holdings area */
2455     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2456     char p;
2457     ChessSquare piece;
2458
2459     if(gameInfo.holdingsWidth < 2)  return;
2460     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2461         return; // prevent overwriting by pre-board holdings
2462
2463     if( (int)lowestPiece >= BlackPawn ) {
2464         holdingsColumn = 0;
2465         countsColumn = 1;
2466         holdingsStartRow = BOARD_HEIGHT-1;
2467         direction = -1;
2468     } else {
2469         holdingsColumn = BOARD_WIDTH-1;
2470         countsColumn = BOARD_WIDTH-2;
2471         holdingsStartRow = 0;
2472         direction = 1;
2473     }
2474
2475     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2476         board[i][holdingsColumn] = EmptySquare;
2477         board[i][countsColumn]   = (ChessSquare) 0;
2478     }
2479     while( (p=*holdings++) != NULLCHAR ) {
2480         piece = CharToPiece( ToUpper(p) );
2481         if(piece == EmptySquare) continue;
2482         /*j = (int) piece - (int) WhitePawn;*/
2483         j = PieceToNumber(piece);
2484         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2485         if(j < 0) continue;               /* should not happen */
2486         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2487         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2488         board[holdingsStartRow+j*direction][countsColumn]++;
2489     }
2490 }
2491
2492
2493 void
2494 VariantSwitch (Board board, VariantClass newVariant)
2495 {
2496    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2497    static Board oldBoard;
2498
2499    startedFromPositionFile = FALSE;
2500    if(gameInfo.variant == newVariant) return;
2501
2502    /* [HGM] This routine is called each time an assignment is made to
2503     * gameInfo.variant during a game, to make sure the board sizes
2504     * are set to match the new variant. If that means adding or deleting
2505     * holdings, we shift the playing board accordingly
2506     * This kludge is needed because in ICS observe mode, we get boards
2507     * of an ongoing game without knowing the variant, and learn about the
2508     * latter only later. This can be because of the move list we requested,
2509     * in which case the game history is refilled from the beginning anyway,
2510     * but also when receiving holdings of a crazyhouse game. In the latter
2511     * case we want to add those holdings to the already received position.
2512     */
2513
2514
2515    if (appData.debugMode) {
2516      fprintf(debugFP, "Switch board from %s to %s\n",
2517              VariantName(gameInfo.variant), VariantName(newVariant));
2518      setbuf(debugFP, NULL);
2519    }
2520    shuffleOpenings = 0;       /* [HGM] shuffle */
2521    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2522    switch(newVariant)
2523      {
2524      case VariantShogi:
2525        newWidth = 9;  newHeight = 9;
2526        gameInfo.holdingsSize = 7;
2527      case VariantBughouse:
2528      case VariantCrazyhouse:
2529        newHoldingsWidth = 2; break;
2530      case VariantGreat:
2531        newWidth = 10;
2532      case VariantSuper:
2533        newHoldingsWidth = 2;
2534        gameInfo.holdingsSize = 8;
2535        break;
2536      case VariantGothic:
2537      case VariantCapablanca:
2538      case VariantCapaRandom:
2539        newWidth = 10;
2540      default:
2541        newHoldingsWidth = gameInfo.holdingsSize = 0;
2542      };
2543
2544    if(newWidth  != gameInfo.boardWidth  ||
2545       newHeight != gameInfo.boardHeight ||
2546       newHoldingsWidth != gameInfo.holdingsWidth ) {
2547
2548      /* shift position to new playing area, if needed */
2549      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2550        for(i=0; i<BOARD_HEIGHT; i++)
2551          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2552            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2553              board[i][j];
2554        for(i=0; i<newHeight; i++) {
2555          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2556          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2557        }
2558      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2559        for(i=0; i<BOARD_HEIGHT; i++)
2560          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2561            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2562              board[i][j];
2563      }
2564      board[HOLDINGS_SET] = 0;
2565      gameInfo.boardWidth  = newWidth;
2566      gameInfo.boardHeight = newHeight;
2567      gameInfo.holdingsWidth = newHoldingsWidth;
2568      gameInfo.variant = newVariant;
2569      InitDrawingSizes(-2, 0);
2570    } else gameInfo.variant = newVariant;
2571    CopyBoard(oldBoard, board);   // remember correctly formatted board
2572      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2573    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2574 }
2575
2576 static int loggedOn = FALSE;
2577
2578 /*-- Game start info cache: --*/
2579 int gs_gamenum;
2580 char gs_kind[MSG_SIZ];
2581 static char player1Name[128] = "";
2582 static char player2Name[128] = "";
2583 static char cont_seq[] = "\n\\   ";
2584 static int player1Rating = -1;
2585 static int player2Rating = -1;
2586 /*----------------------------*/
2587
2588 ColorClass curColor = ColorNormal;
2589 int suppressKibitz = 0;
2590
2591 // [HGM] seekgraph
2592 Boolean soughtPending = FALSE;
2593 Boolean seekGraphUp;
2594 #define MAX_SEEK_ADS 200
2595 #define SQUARE 0x80
2596 char *seekAdList[MAX_SEEK_ADS];
2597 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2598 float tcList[MAX_SEEK_ADS];
2599 char colorList[MAX_SEEK_ADS];
2600 int nrOfSeekAds = 0;
2601 int minRating = 1010, maxRating = 2800;
2602 int hMargin = 10, vMargin = 20, h, w;
2603 extern int squareSize, lineGap;
2604
2605 void
2606 PlotSeekAd (int i)
2607 {
2608         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2609         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2610         if(r < minRating+100 && r >=0 ) r = minRating+100;
2611         if(r > maxRating) r = maxRating;
2612         if(tc < 1.f) tc = 1.f;
2613         if(tc > 95.f) tc = 95.f;
2614         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2615         y = ((double)r - minRating)/(maxRating - minRating)
2616             * (h-vMargin-squareSize/8-1) + vMargin;
2617         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2618         if(strstr(seekAdList[i], " u ")) color = 1;
2619         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2620            !strstr(seekAdList[i], "bullet") &&
2621            !strstr(seekAdList[i], "blitz") &&
2622            !strstr(seekAdList[i], "standard") ) color = 2;
2623         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2624         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2625 }
2626
2627 void
2628 PlotSingleSeekAd (int i)
2629 {
2630         PlotSeekAd(i);
2631 }
2632
2633 void
2634 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2635 {
2636         char buf[MSG_SIZ], *ext = "";
2637         VariantClass v = StringToVariant(type);
2638         if(strstr(type, "wild")) {
2639             ext = type + 4; // append wild number
2640             if(v == VariantFischeRandom) type = "chess960"; else
2641             if(v == VariantLoadable) type = "setup"; else
2642             type = VariantName(v);
2643         }
2644         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2645         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2646             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2647             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2648             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2649             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2650             seekNrList[nrOfSeekAds] = nr;
2651             zList[nrOfSeekAds] = 0;
2652             seekAdList[nrOfSeekAds++] = StrSave(buf);
2653             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2654         }
2655 }
2656
2657 void
2658 EraseSeekDot (int i)
2659 {
2660     int x = xList[i], y = yList[i], d=squareSize/4, k;
2661     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2662     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2663     // now replot every dot that overlapped
2664     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2665         int xx = xList[k], yy = yList[k];
2666         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2667             DrawSeekDot(xx, yy, colorList[k]);
2668     }
2669 }
2670
2671 void
2672 RemoveSeekAd (int nr)
2673 {
2674         int i;
2675         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2676             EraseSeekDot(i);
2677             if(seekAdList[i]) free(seekAdList[i]);
2678             seekAdList[i] = seekAdList[--nrOfSeekAds];
2679             seekNrList[i] = seekNrList[nrOfSeekAds];
2680             ratingList[i] = ratingList[nrOfSeekAds];
2681             colorList[i]  = colorList[nrOfSeekAds];
2682             tcList[i] = tcList[nrOfSeekAds];
2683             xList[i]  = xList[nrOfSeekAds];
2684             yList[i]  = yList[nrOfSeekAds];
2685             zList[i]  = zList[nrOfSeekAds];
2686             seekAdList[nrOfSeekAds] = NULL;
2687             break;
2688         }
2689 }
2690
2691 Boolean
2692 MatchSoughtLine (char *line)
2693 {
2694     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2695     int nr, base, inc, u=0; char dummy;
2696
2697     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2698        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2699        (u=1) &&
2700        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2701         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2702         // match: compact and save the line
2703         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2704         return TRUE;
2705     }
2706     return FALSE;
2707 }
2708
2709 int
2710 DrawSeekGraph ()
2711 {
2712     int i;
2713     if(!seekGraphUp) return FALSE;
2714     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2715     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2716
2717     DrawSeekBackground(0, 0, w, h);
2718     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2719     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2720     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2721         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2722         yy = h-1-yy;
2723         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2724         if(i%500 == 0) {
2725             char buf[MSG_SIZ];
2726             snprintf(buf, MSG_SIZ, "%d", i);
2727             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2728         }
2729     }
2730     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2731     for(i=1; i<100; i+=(i<10?1:5)) {
2732         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2733         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2734         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2738         }
2739     }
2740     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2741     return TRUE;
2742 }
2743
2744 int
2745 SeekGraphClick (ClickType click, int x, int y, int moving)
2746 {
2747     static int lastDown = 0, displayed = 0, lastSecond;
2748     if(y < 0) return FALSE;
2749     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2750         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2751         if(!seekGraphUp) return FALSE;
2752         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2753         DrawPosition(TRUE, NULL);
2754         return TRUE;
2755     }
2756     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2757         if(click == Release || moving) return FALSE;
2758         nrOfSeekAds = 0;
2759         soughtPending = TRUE;
2760         SendToICS(ics_prefix);
2761         SendToICS("sought\n"); // should this be "sought all"?
2762     } else { // issue challenge based on clicked ad
2763         int dist = 10000; int i, closest = 0, second = 0;
2764         for(i=0; i<nrOfSeekAds; i++) {
2765             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2766             if(d < dist) { dist = d; closest = i; }
2767             second += (d - zList[i] < 120); // count in-range ads
2768             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2769         }
2770         if(dist < 120) {
2771             char buf[MSG_SIZ];
2772             second = (second > 1);
2773             if(displayed != closest || second != lastSecond) {
2774                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2775                 lastSecond = second; displayed = closest;
2776             }
2777             if(click == Press) {
2778                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2779                 lastDown = closest;
2780                 return TRUE;
2781             } // on press 'hit', only show info
2782             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2783             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2784             SendToICS(ics_prefix);
2785             SendToICS(buf);
2786             return TRUE; // let incoming board of started game pop down the graph
2787         } else if(click == Release) { // release 'miss' is ignored
2788             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2789             if(moving == 2) { // right up-click
2790                 nrOfSeekAds = 0; // refresh graph
2791                 soughtPending = TRUE;
2792                 SendToICS(ics_prefix);
2793                 SendToICS("sought\n"); // should this be "sought all"?
2794             }
2795             return TRUE;
2796         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2797         // press miss or release hit 'pop down' seek graph
2798         seekGraphUp = FALSE;
2799         DrawPosition(TRUE, NULL);
2800     }
2801     return TRUE;
2802 }
2803
2804 void
2805 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2806 {
2807 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2808 #define STARTED_NONE 0
2809 #define STARTED_MOVES 1
2810 #define STARTED_BOARD 2
2811 #define STARTED_OBSERVE 3
2812 #define STARTED_HOLDINGS 4
2813 #define STARTED_CHATTER 5
2814 #define STARTED_COMMENT 6
2815 #define STARTED_MOVES_NOHIDE 7
2816
2817     static int started = STARTED_NONE;
2818     static char parse[20000];
2819     static int parse_pos = 0;
2820     static char buf[BUF_SIZE + 1];
2821     static int firstTime = TRUE, intfSet = FALSE;
2822     static ColorClass prevColor = ColorNormal;
2823     static int savingComment = FALSE;
2824     static int cmatch = 0; // continuation sequence match
2825     char *bp;
2826     char str[MSG_SIZ];
2827     int i, oldi;
2828     int buf_len;
2829     int next_out;
2830     int tkind;
2831     int backup;    /* [DM] For zippy color lines */
2832     char *p;
2833     char talker[MSG_SIZ]; // [HGM] chat
2834     int channel, collective=0;
2835
2836     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2837
2838     if (appData.debugMode) {
2839       if (!error) {
2840         fprintf(debugFP, "<ICS: ");
2841         show_bytes(debugFP, data, count);
2842         fprintf(debugFP, "\n");
2843       }
2844     }
2845
2846     if (appData.debugMode) { int f = forwardMostMove;
2847         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2848                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2849                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2850     }
2851     if (count > 0) {
2852         /* If last read ended with a partial line that we couldn't parse,
2853            prepend it to the new read and try again. */
2854         if (leftover_len > 0) {
2855             for (i=0; i<leftover_len; i++)
2856               buf[i] = buf[leftover_start + i];
2857         }
2858
2859     /* copy new characters into the buffer */
2860     bp = buf + leftover_len;
2861     buf_len=leftover_len;
2862     for (i=0; i<count; i++)
2863     {
2864         // ignore these
2865         if (data[i] == '\r')
2866             continue;
2867
2868         // join lines split by ICS?
2869         if (!appData.noJoin)
2870         {
2871             /*
2872                 Joining just consists of finding matches against the
2873                 continuation sequence, and discarding that sequence
2874                 if found instead of copying it.  So, until a match
2875                 fails, there's nothing to do since it might be the
2876                 complete sequence, and thus, something we don't want
2877                 copied.
2878             */
2879             if (data[i] == cont_seq[cmatch])
2880             {
2881                 cmatch++;
2882                 if (cmatch == strlen(cont_seq))
2883                 {
2884                     cmatch = 0; // complete match.  just reset the counter
2885
2886                     /*
2887                         it's possible for the ICS to not include the space
2888                         at the end of the last word, making our [correct]
2889                         join operation fuse two separate words.  the server
2890                         does this when the space occurs at the width setting.
2891                     */
2892                     if (!buf_len || buf[buf_len-1] != ' ')
2893                     {
2894                         *bp++ = ' ';
2895                         buf_len++;
2896                     }
2897                 }
2898                 continue;
2899             }
2900             else if (cmatch)
2901             {
2902                 /*
2903                     match failed, so we have to copy what matched before
2904                     falling through and copying this character.  In reality,
2905                     this will only ever be just the newline character, but
2906                     it doesn't hurt to be precise.
2907                 */
2908                 strncpy(bp, cont_seq, cmatch);
2909                 bp += cmatch;
2910                 buf_len += cmatch;
2911                 cmatch = 0;
2912             }
2913         }
2914
2915         // copy this char
2916         *bp++ = data[i];
2917         buf_len++;
2918     }
2919
2920         buf[buf_len] = NULLCHAR;
2921 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2922         next_out = 0;
2923         leftover_start = 0;
2924
2925         i = 0;
2926         while (i < buf_len) {
2927             /* Deal with part of the TELNET option negotiation
2928                protocol.  We refuse to do anything beyond the
2929                defaults, except that we allow the WILL ECHO option,
2930                which ICS uses to turn off password echoing when we are
2931                directly connected to it.  We reject this option
2932                if localLineEditing mode is on (always on in xboard)
2933                and we are talking to port 23, which might be a real
2934                telnet server that will try to keep WILL ECHO on permanently.
2935              */
2936             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2937                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2938                 unsigned char option;
2939                 oldi = i;
2940                 switch ((unsigned char) buf[++i]) {
2941                   case TN_WILL:
2942                     if (appData.debugMode)
2943                       fprintf(debugFP, "\n<WILL ");
2944                     switch (option = (unsigned char) buf[++i]) {
2945                       case TN_ECHO:
2946                         if (appData.debugMode)
2947                           fprintf(debugFP, "ECHO ");
2948                         /* Reply only if this is a change, according
2949                            to the protocol rules. */
2950                         if (remoteEchoOption) break;
2951                         if (appData.localLineEditing &&
2952                             atoi(appData.icsPort) == TN_PORT) {
2953                             TelnetRequest(TN_DONT, TN_ECHO);
2954                         } else {
2955                             EchoOff();
2956                             TelnetRequest(TN_DO, TN_ECHO);
2957                             remoteEchoOption = TRUE;
2958                         }
2959                         break;
2960                       default:
2961                         if (appData.debugMode)
2962                           fprintf(debugFP, "%d ", option);
2963                         /* Whatever this is, we don't want it. */
2964                         TelnetRequest(TN_DONT, option);
2965                         break;
2966                     }
2967                     break;
2968                   case TN_WONT:
2969                     if (appData.debugMode)
2970                       fprintf(debugFP, "\n<WONT ");
2971                     switch (option = (unsigned char) buf[++i]) {
2972                       case TN_ECHO:
2973                         if (appData.debugMode)
2974                           fprintf(debugFP, "ECHO ");
2975                         /* Reply only if this is a change, according
2976                            to the protocol rules. */
2977                         if (!remoteEchoOption) break;
2978                         EchoOn();
2979                         TelnetRequest(TN_DONT, TN_ECHO);
2980                         remoteEchoOption = FALSE;
2981                         break;
2982                       default:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "%d ", (unsigned char) option);
2985                         /* Whatever this is, it must already be turned
2986                            off, because we never agree to turn on
2987                            anything non-default, so according to the
2988                            protocol rules, we don't reply. */
2989                         break;
2990                     }
2991                     break;
2992                   case TN_DO:
2993                     if (appData.debugMode)
2994                       fprintf(debugFP, "\n<DO ");
2995                     switch (option = (unsigned char) buf[++i]) {
2996                       default:
2997                         /* Whatever this is, we refuse to do it. */
2998                         if (appData.debugMode)
2999                           fprintf(debugFP, "%d ", option);
3000                         TelnetRequest(TN_WONT, option);
3001                         break;
3002                     }
3003                     break;
3004                   case TN_DONT:
3005                     if (appData.debugMode)
3006                       fprintf(debugFP, "\n<DONT ");
3007                     switch (option = (unsigned char) buf[++i]) {
3008                       default:
3009                         if (appData.debugMode)
3010                           fprintf(debugFP, "%d ", option);
3011                         /* Whatever this is, we are already not doing
3012                            it, because we never agree to do anything
3013                            non-default, so according to the protocol
3014                            rules, we don't reply. */
3015                         break;
3016                     }
3017                     break;
3018                   case TN_IAC:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<IAC ");
3021                     /* Doubled IAC; pass it through */
3022                     i--;
3023                     break;
3024                   default:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3027                     /* Drop all other telnet commands on the floor */
3028                     break;
3029                 }
3030                 if (oldi > next_out)
3031                   SendToPlayer(&buf[next_out], oldi - next_out);
3032                 if (++i > next_out)
3033                   next_out = i;
3034                 continue;
3035             }
3036
3037             /* OK, this at least will *usually* work */
3038             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3039                 loggedOn = TRUE;
3040             }
3041
3042             if (loggedOn && !intfSet) {
3043                 if (ics_type == ICS_ICC) {
3044                   snprintf(str, MSG_SIZ,
3045                           "/set-quietly interface %s\n/set-quietly style 12\n",
3046                           programVersion);
3047                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3048                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3049                 } else if (ics_type == ICS_CHESSNET) {
3050                   snprintf(str, MSG_SIZ, "/style 12\n");
3051                 } else {
3052                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3053                   strcat(str, programVersion);
3054                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3055                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3056                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3057 #ifdef WIN32
3058                   strcat(str, "$iset nohighlight 1\n");
3059 #endif
3060                   strcat(str, "$iset lock 1\n$style 12\n");
3061                 }
3062                 SendToICS(str);
3063                 NotifyFrontendLogin();
3064                 intfSet = TRUE;
3065             }
3066
3067             if (started == STARTED_COMMENT) {
3068                 /* Accumulate characters in comment */
3069                 parse[parse_pos++] = buf[i];
3070                 if (buf[i] == '\n') {
3071                     parse[parse_pos] = NULLCHAR;
3072                     if(chattingPartner>=0) {
3073                         char mess[MSG_SIZ];
3074                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3075                         OutputChatMessage(chattingPartner, mess);
3076                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3077                             int p;
3078                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3079                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3080                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3081                                 OutputChatMessage(p, mess);
3082                                 break;
3083                             }
3084                         }
3085                         chattingPartner = -1;
3086                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3087                         collective = 0;
3088                     } else
3089                     if(!suppressKibitz) // [HGM] kibitz
3090                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3091                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3092                         int nrDigit = 0, nrAlph = 0, j;
3093                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3094                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3095                         parse[parse_pos] = NULLCHAR;
3096                         // try to be smart: if it does not look like search info, it should go to
3097                         // ICS interaction window after all, not to engine-output window.
3098                         for(j=0; j<parse_pos; j++) { // count letters and digits
3099                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3100                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3101                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3102                         }
3103                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3104                             int depth=0; float score;
3105                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3106                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3107                                 pvInfoList[forwardMostMove-1].depth = depth;
3108                                 pvInfoList[forwardMostMove-1].score = 100*score;
3109                             }
3110                             OutputKibitz(suppressKibitz, parse);
3111                         } else {
3112                             char tmp[MSG_SIZ];
3113                             if(gameMode == IcsObserving) // restore original ICS messages
3114                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3115                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3116                             else
3117                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3118                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3119                             SendToPlayer(tmp, strlen(tmp));
3120                         }
3121                         next_out = i+1; // [HGM] suppress printing in ICS window
3122                     }
3123                     started = STARTED_NONE;
3124                 } else {
3125                     /* Don't match patterns against characters in comment */
3126                     i++;
3127                     continue;
3128                 }
3129             }
3130             if (started == STARTED_CHATTER) {
3131                 if (buf[i] != '\n') {
3132                     /* Don't match patterns against characters in chatter */
3133                     i++;
3134                     continue;
3135                 }
3136                 started = STARTED_NONE;
3137                 if(suppressKibitz) next_out = i+1;
3138             }
3139
3140             /* Kludge to deal with rcmd protocol */
3141             if (firstTime && looking_at(buf, &i, "\001*")) {
3142                 DisplayFatalError(&buf[1], 0, 1);
3143                 continue;
3144             } else {
3145                 firstTime = FALSE;
3146             }
3147
3148             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3149                 ics_type = ICS_ICC;
3150                 ics_prefix = "/";
3151                 if (appData.debugMode)
3152                   fprintf(debugFP, "ics_type %d\n", ics_type);
3153                 continue;
3154             }
3155             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3156                 ics_type = ICS_FICS;
3157                 ics_prefix = "$";
3158                 if (appData.debugMode)
3159                   fprintf(debugFP, "ics_type %d\n", ics_type);
3160                 continue;
3161             }
3162             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3163                 ics_type = ICS_CHESSNET;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169
3170             if (!loggedOn &&
3171                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3172                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3173                  looking_at(buf, &i, "will be \"*\""))) {
3174               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3175               continue;
3176             }
3177
3178             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3179               char buf[MSG_SIZ];
3180               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3181               DisplayIcsInteractionTitle(buf);
3182               have_set_title = TRUE;
3183             }
3184
3185             /* skip finger notes */
3186             if (started == STARTED_NONE &&
3187                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3188                  (buf[i] == '1' && buf[i+1] == '0')) &&
3189                 buf[i+2] == ':' && buf[i+3] == ' ') {
3190               started = STARTED_CHATTER;
3191               i += 3;
3192               continue;
3193             }
3194
3195             oldi = i;
3196             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3197             if(appData.seekGraph) {
3198                 if(soughtPending && MatchSoughtLine(buf+i)) {
3199                     i = strstr(buf+i, "rated") - buf;
3200                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201                     next_out = leftover_start = i;
3202                     started = STARTED_CHATTER;
3203                     suppressKibitz = TRUE;
3204                     continue;
3205                 }
3206                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3207                         && looking_at(buf, &i, "* ads displayed")) {
3208                     soughtPending = FALSE;
3209                     seekGraphUp = TRUE;
3210                     DrawSeekGraph();
3211                     continue;
3212                 }
3213                 if(appData.autoRefresh) {
3214                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3215                         int s = (ics_type == ICS_ICC); // ICC format differs
3216                         if(seekGraphUp)
3217                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3218                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3219                         looking_at(buf, &i, "*% "); // eat prompt
3220                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3221                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                         next_out = i; // suppress
3223                         continue;
3224                     }
3225                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3226                         char *p = star_match[0];
3227                         while(*p) {
3228                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3229                             while(*p && *p++ != ' '); // next
3230                         }
3231                         looking_at(buf, &i, "*% "); // eat prompt
3232                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3233                         next_out = i;
3234                         continue;
3235                     }
3236                 }
3237             }
3238
3239             /* skip formula vars */
3240             if (started == STARTED_NONE &&
3241                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3242               started = STARTED_CHATTER;
3243               i += 3;
3244               continue;
3245             }
3246
3247             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3248             if (appData.autoKibitz && started == STARTED_NONE &&
3249                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3250                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3251                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3252                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3253                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3254                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3255                         suppressKibitz = TRUE;
3256                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3257                         next_out = i;
3258                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3259                                 && (gameMode == IcsPlayingWhite)) ||
3260                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3261                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3262                             started = STARTED_CHATTER; // own kibitz we simply discard
3263                         else {
3264                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3265                             parse_pos = 0; parse[0] = NULLCHAR;
3266                             savingComment = TRUE;
3267                             suppressKibitz = gameMode != IcsObserving ? 2 :
3268                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3269                         }
3270                         continue;
3271                 } else
3272                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3273                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3274                          && atoi(star_match[0])) {
3275                     // suppress the acknowledgements of our own autoKibitz
3276                     char *p;
3277                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3278                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3279                     SendToPlayer(star_match[0], strlen(star_match[0]));
3280                     if(looking_at(buf, &i, "*% ")) // eat prompt
3281                         suppressKibitz = FALSE;
3282                     next_out = i;
3283                     continue;
3284                 }
3285             } // [HGM] kibitz: end of patch
3286
3287             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3288
3289             // [HGM] chat: intercept tells by users for which we have an open chat window
3290             channel = -1;
3291             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3292                                            looking_at(buf, &i, "* whispers:") ||
3293                                            looking_at(buf, &i, "* kibitzes:") ||
3294                                            looking_at(buf, &i, "* shouts:") ||
3295                                            looking_at(buf, &i, "* c-shouts:") ||
3296                                            looking_at(buf, &i, "--> * ") ||
3297                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3298                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3299                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3300                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3301                 int p;
3302                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3303                 chattingPartner = -1; collective = 0;
3304
3305                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3306                 for(p=0; p<MAX_CHAT; p++) {
3307                     collective = 1;
3308                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3309                     talker[0] = '['; strcat(talker, "] ");
3310                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3311                     chattingPartner = p; break;
3312                     }
3313                 } else
3314                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3315                 for(p=0; p<MAX_CHAT; p++) {
3316                     collective = 1;
3317                     if(!strcmp("kibitzes", chatPartner[p])) {
3318                         talker[0] = '['; strcat(talker, "] ");
3319                         chattingPartner = p; break;
3320                     }
3321                 } else
3322                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3323                 for(p=0; p<MAX_CHAT; p++) {
3324                     collective = 1;
3325                     if(!strcmp("whispers", chatPartner[p])) {
3326                         talker[0] = '['; strcat(talker, "] ");
3327                         chattingPartner = p; break;
3328                     }
3329                 } else
3330                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3331                   if(buf[i-8] == '-' && buf[i-3] == 't')
3332                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3333                     collective = 1;
3334                     if(!strcmp("c-shouts", chatPartner[p])) {
3335                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3336                         chattingPartner = p; break;
3337                     }
3338                   }
3339                   if(chattingPartner < 0)
3340                   for(p=0; p<MAX_CHAT; p++) {
3341                     collective = 1;
3342                     if(!strcmp("shouts", chatPartner[p])) {
3343                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3344                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3345                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                 }
3350                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3351                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3352                     talker[0] = 0;
3353                     Colorize(ColorTell, FALSE);
3354                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3355                     collective |= 2;
3356                     chattingPartner = p; break;
3357                 }
3358                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3359                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3360                     started = STARTED_COMMENT;
3361                     parse_pos = 0; parse[0] = NULLCHAR;
3362                     savingComment = 3 + chattingPartner; // counts as TRUE
3363                     if(collective == 3) i = oldi; else {
3364                         suppressKibitz = TRUE;
3365                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3366                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3367                         continue;
3368                     }
3369                 }
3370             } // [HGM] chat: end of patch
3371
3372           backup = i;
3373             if (appData.zippyTalk || appData.zippyPlay) {
3374                 /* [DM] Backup address for color zippy lines */
3375 #if ZIPPY
3376                if (loggedOn == TRUE)
3377                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3378                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3379 #endif
3380             } // [DM] 'else { ' deleted
3381                 if (
3382                     /* Regular tells and says */
3383                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3384                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3385                     looking_at(buf, &i, "* says: ") ||
3386                     /* Don't color "message" or "messages" output */
3387                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3388                     looking_at(buf, &i, "*. * at *:*: ") ||
3389                     looking_at(buf, &i, "--* (*:*): ") ||
3390                     /* Message notifications (same color as tells) */
3391                     looking_at(buf, &i, "* has left a message ") ||
3392                     looking_at(buf, &i, "* just sent you a message:\n") ||
3393                     /* Whispers and kibitzes */
3394                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3395                     looking_at(buf, &i, "* kibitzes: ") ||
3396                     /* Channel tells */
3397                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3398
3399                   if (tkind == 1 && strchr(star_match[0], ':')) {
3400                       /* Avoid "tells you:" spoofs in channels */
3401                      tkind = 3;
3402                   }
3403                   if (star_match[0][0] == NULLCHAR ||
3404                       strchr(star_match[0], ' ') ||
3405                       (tkind == 3 && strchr(star_match[1], ' '))) {
3406                     /* Reject bogus matches */
3407                     i = oldi;
3408                   } else {
3409                     if (appData.colorize) {
3410                       if (oldi > next_out) {
3411                         SendToPlayer(&buf[next_out], oldi - next_out);
3412                         next_out = oldi;
3413                       }
3414                       switch (tkind) {
3415                       case 1:
3416                         Colorize(ColorTell, FALSE);
3417                         curColor = ColorTell;
3418                         break;
3419                       case 2:
3420                         Colorize(ColorKibitz, FALSE);
3421                         curColor = ColorKibitz;
3422                         break;
3423                       case 3:
3424                         p = strrchr(star_match[1], '(');
3425                         if (p == NULL) {
3426                           p = star_match[1];
3427                         } else {
3428                           p++;
3429                         }
3430                         if (atoi(p) == 1) {
3431                           Colorize(ColorChannel1, FALSE);
3432                           curColor = ColorChannel1;
3433                         } else {
3434                           Colorize(ColorChannel, FALSE);
3435                           curColor = ColorChannel;
3436                         }
3437                         break;
3438                       case 5:
3439                         curColor = ColorNormal;
3440                         break;
3441                       }
3442                     }
3443                     if (started == STARTED_NONE && appData.autoComment &&
3444                         (gameMode == IcsObserving ||
3445                          gameMode == IcsPlayingWhite ||
3446                          gameMode == IcsPlayingBlack)) {
3447                       parse_pos = i - oldi;
3448                       memcpy(parse, &buf[oldi], parse_pos);
3449                       parse[parse_pos] = NULLCHAR;
3450                       started = STARTED_COMMENT;
3451                       savingComment = TRUE;
3452                     } else if(collective != 3) {
3453                       started = STARTED_CHATTER;
3454                       savingComment = FALSE;
3455                     }
3456                     loggedOn = TRUE;
3457                     continue;
3458                   }
3459                 }
3460
3461                 if (looking_at(buf, &i, "* s-shouts: ") ||
3462                     looking_at(buf, &i, "* c-shouts: ")) {
3463                     if (appData.colorize) {
3464                         if (oldi > next_out) {
3465                             SendToPlayer(&buf[next_out], oldi - next_out);
3466                             next_out = oldi;
3467                         }
3468                         Colorize(ColorSShout, FALSE);
3469                         curColor = ColorSShout;
3470                     }
3471                     loggedOn = TRUE;
3472                     started = STARTED_CHATTER;
3473                     continue;
3474                 }
3475
3476                 if (looking_at(buf, &i, "--->")) {
3477                     loggedOn = TRUE;
3478                     continue;
3479                 }
3480
3481                 if (looking_at(buf, &i, "* shouts: ") ||
3482                     looking_at(buf, &i, "--> ")) {
3483                     if (appData.colorize) {
3484                         if (oldi > next_out) {
3485                             SendToPlayer(&buf[next_out], oldi - next_out);
3486                             next_out = oldi;
3487                         }
3488                         Colorize(ColorShout, FALSE);
3489                         curColor = ColorShout;
3490                     }
3491                     loggedOn = TRUE;
3492                     started = STARTED_CHATTER;
3493                     continue;
3494                 }
3495
3496                 if (looking_at( buf, &i, "Challenge:")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorChallenge, FALSE);
3503                         curColor = ColorChallenge;
3504                     }
3505                     loggedOn = TRUE;
3506                     continue;
3507                 }
3508
3509                 if (looking_at(buf, &i, "* offers you") ||
3510                     looking_at(buf, &i, "* offers to be") ||
3511                     looking_at(buf, &i, "* would like to") ||
3512                     looking_at(buf, &i, "* requests to") ||
3513                     looking_at(buf, &i, "Your opponent offers") ||
3514                     looking_at(buf, &i, "Your opponent requests")) {
3515
3516                     if (appData.colorize) {
3517                         if (oldi > next_out) {
3518                             SendToPlayer(&buf[next_out], oldi - next_out);
3519                             next_out = oldi;
3520                         }
3521                         Colorize(ColorRequest, FALSE);
3522                         curColor = ColorRequest;
3523                     }
3524                     continue;
3525                 }
3526
3527                 if (looking_at(buf, &i, "* (*) seeking")) {
3528                     if (appData.colorize) {
3529                         if (oldi > next_out) {
3530                             SendToPlayer(&buf[next_out], oldi - next_out);
3531                             next_out = oldi;
3532                         }
3533                         Colorize(ColorSeek, FALSE);
3534                         curColor = ColorSeek;
3535                     }
3536                     continue;
3537             }
3538
3539           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3540
3541             if (looking_at(buf, &i, "\\   ")) {
3542                 if (prevColor != ColorNormal) {
3543                     if (oldi > next_out) {
3544                         SendToPlayer(&buf[next_out], oldi - next_out);
3545                         next_out = oldi;
3546                     }
3547                     Colorize(prevColor, TRUE);
3548                     curColor = prevColor;
3549                 }
3550                 if (savingComment) {
3551                     parse_pos = i - oldi;
3552                     memcpy(parse, &buf[oldi], parse_pos);
3553                     parse[parse_pos] = NULLCHAR;
3554                     started = STARTED_COMMENT;
3555                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3556                         chattingPartner = savingComment - 3; // kludge to remember the box
3557                 } else {
3558                     started = STARTED_CHATTER;
3559                 }
3560                 continue;
3561             }
3562
3563             if (looking_at(buf, &i, "Black Strength :") ||
3564                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3565                 looking_at(buf, &i, "<10>") ||
3566                 looking_at(buf, &i, "#@#")) {
3567                 /* Wrong board style */
3568                 loggedOn = TRUE;
3569                 SendToICS(ics_prefix);
3570                 SendToICS("set style 12\n");
3571                 SendToICS(ics_prefix);
3572                 SendToICS("refresh\n");
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "login:")) {
3577               if (!have_sent_ICS_logon) {
3578                 if(ICSInitScript())
3579                   have_sent_ICS_logon = 1;
3580                 else // no init script was found
3581                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3582               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3583                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3584               }
3585                 continue;
3586             }
3587
3588             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3589                 (looking_at(buf, &i, "\n<12> ") ||
3590                  looking_at(buf, &i, "<12> "))) {
3591                 loggedOn = TRUE;
3592                 if (oldi > next_out) {
3593                     SendToPlayer(&buf[next_out], oldi - next_out);
3594                 }
3595                 next_out = i;
3596                 started = STARTED_BOARD;
3597                 parse_pos = 0;
3598                 continue;
3599             }
3600
3601             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3602                 looking_at(buf, &i, "<b1> ")) {
3603                 if (oldi > next_out) {
3604                     SendToPlayer(&buf[next_out], oldi - next_out);
3605                 }
3606                 next_out = i;
3607                 started = STARTED_HOLDINGS;
3608                 parse_pos = 0;
3609                 continue;
3610             }
3611
3612             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3613                 loggedOn = TRUE;
3614                 /* Header for a move list -- first line */
3615
3616                 switch (ics_getting_history) {
3617                   case H_FALSE:
3618                     switch (gameMode) {
3619                       case IcsIdle:
3620                       case BeginningOfGame:
3621                         /* User typed "moves" or "oldmoves" while we
3622                            were idle.  Pretend we asked for these
3623                            moves and soak them up so user can step
3624                            through them and/or save them.
3625                            */
3626                         Reset(FALSE, TRUE);
3627                         gameMode = IcsObserving;
3628                         ModeHighlight();
3629                         ics_gamenum = -1;
3630                         ics_getting_history = H_GOT_UNREQ_HEADER;
3631                         break;
3632                       case EditGame: /*?*/
3633                       case EditPosition: /*?*/
3634                         /* Should above feature work in these modes too? */
3635                         /* For now it doesn't */
3636                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3637                         break;
3638                       default:
3639                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3640                         break;
3641                     }
3642                     break;
3643                   case H_REQUESTED:
3644                     /* Is this the right one? */
3645                     if (gameInfo.white && gameInfo.black &&
3646                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3647                         strcmp(gameInfo.black, star_match[2]) == 0) {
3648                         /* All is well */
3649                         ics_getting_history = H_GOT_REQ_HEADER;
3650                     }
3651                     break;
3652                   case H_GOT_REQ_HEADER:
3653                   case H_GOT_UNREQ_HEADER:
3654                   case H_GOT_UNWANTED_HEADER:
3655                   case H_GETTING_MOVES:
3656                     /* Should not happen */
3657                     DisplayError(_("Error gathering move list: two headers"), 0);
3658                     ics_getting_history = H_FALSE;
3659                     break;
3660                 }
3661
3662                 /* Save player ratings into gameInfo if needed */
3663                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3664                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3665                     (gameInfo.whiteRating == -1 ||
3666                      gameInfo.blackRating == -1)) {
3667
3668                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3669                     gameInfo.blackRating = string_to_rating(star_match[3]);
3670                     if (appData.debugMode)
3671                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3672                               gameInfo.whiteRating, gameInfo.blackRating);
3673                 }
3674                 continue;
3675             }
3676
3677             if (looking_at(buf, &i,
3678               "* * match, initial time: * minute*, increment: * second")) {
3679                 /* Header for a move list -- second line */
3680                 /* Initial board will follow if this is a wild game */
3681                 if (gameInfo.event != NULL) free(gameInfo.event);
3682                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3683                 gameInfo.event = StrSave(str);
3684                 /* [HGM] we switched variant. Translate boards if needed. */
3685                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3686                 continue;
3687             }
3688
3689             if (looking_at(buf, &i, "Move  ")) {
3690                 /* Beginning of a move list */
3691                 switch (ics_getting_history) {
3692                   case H_FALSE:
3693                     /* Normally should not happen */
3694                     /* Maybe user hit reset while we were parsing */
3695                     break;
3696                   case H_REQUESTED:
3697                     /* Happens if we are ignoring a move list that is not
3698                      * the one we just requested.  Common if the user
3699                      * tries to observe two games without turning off
3700                      * getMoveList */
3701                     break;
3702                   case H_GETTING_MOVES:
3703                     /* Should not happen */
3704                     DisplayError(_("Error gathering move list: nested"), 0);
3705                     ics_getting_history = H_FALSE;
3706                     break;
3707                   case H_GOT_REQ_HEADER:
3708                     ics_getting_history = H_GETTING_MOVES;
3709                     started = STARTED_MOVES;
3710                     parse_pos = 0;
3711                     if (oldi > next_out) {
3712                         SendToPlayer(&buf[next_out], oldi - next_out);
3713                     }
3714                     break;
3715                   case H_GOT_UNREQ_HEADER:
3716                     ics_getting_history = H_GETTING_MOVES;
3717                     started = STARTED_MOVES_NOHIDE;
3718                     parse_pos = 0;
3719                     break;
3720                   case H_GOT_UNWANTED_HEADER:
3721                     ics_getting_history = H_FALSE;
3722                     break;
3723                 }
3724                 continue;
3725             }
3726
3727             if (looking_at(buf, &i, "% ") ||
3728                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3729                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3730                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3731                     soughtPending = FALSE;
3732                     seekGraphUp = TRUE;
3733                     DrawSeekGraph();
3734                 }
3735                 if(suppressKibitz) next_out = i;
3736                 savingComment = FALSE;
3737                 suppressKibitz = 0;
3738                 switch (started) {
3739                   case STARTED_MOVES:
3740                   case STARTED_MOVES_NOHIDE:
3741                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3742                     parse[parse_pos + i - oldi] = NULLCHAR;
3743                     ParseGameHistory(parse);
3744 #if ZIPPY
3745                     if (appData.zippyPlay && first.initDone) {
3746                         FeedMovesToProgram(&first, forwardMostMove);
3747                         if (gameMode == IcsPlayingWhite) {
3748                             if (WhiteOnMove(forwardMostMove)) {
3749                                 if (first.sendTime) {
3750                                   if (first.useColors) {
3751                                     SendToProgram("black\n", &first);
3752                                   }
3753                                   SendTimeRemaining(&first, TRUE);
3754                                 }
3755                                 if (first.useColors) {
3756                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3757                                 }
3758                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3759                                 first.maybeThinking = TRUE;
3760                             } else {
3761                                 if (first.usePlayother) {
3762                                   if (first.sendTime) {
3763                                     SendTimeRemaining(&first, TRUE);
3764                                   }
3765                                   SendToProgram("playother\n", &first);
3766                                   firstMove = FALSE;
3767                                 } else {
3768                                   firstMove = TRUE;
3769                                 }
3770                             }
3771                         } else if (gameMode == IcsPlayingBlack) {
3772                             if (!WhiteOnMove(forwardMostMove)) {
3773                                 if (first.sendTime) {
3774                                   if (first.useColors) {
3775                                     SendToProgram("white\n", &first);
3776                                   }
3777                                   SendTimeRemaining(&first, FALSE);
3778                                 }
3779                                 if (first.useColors) {
3780                                   SendToProgram("black\n", &first);
3781                                 }
3782                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3783                                 first.maybeThinking = TRUE;
3784                             } else {
3785                                 if (first.usePlayother) {
3786                                   if (first.sendTime) {
3787                                     SendTimeRemaining(&first, FALSE);
3788                                   }
3789                                   SendToProgram("playother\n", &first);
3790                                   firstMove = FALSE;
3791                                 } else {
3792                                   firstMove = TRUE;
3793                                 }
3794                             }
3795                         }
3796                     }
3797 #endif
3798                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3799                         /* Moves came from oldmoves or moves command
3800                            while we weren't doing anything else.
3801                            */
3802                         currentMove = forwardMostMove;
3803                         ClearHighlights();/*!!could figure this out*/
3804                         flipView = appData.flipView;
3805                         DrawPosition(TRUE, boards[currentMove]);
3806                         DisplayBothClocks();
3807                         snprintf(str, MSG_SIZ, "%s %s %s",
3808                                 gameInfo.white, _("vs."),  gameInfo.black);
3809                         DisplayTitle(str);
3810                         gameMode = IcsIdle;
3811                     } else {
3812                         /* Moves were history of an active game */
3813                         if (gameInfo.resultDetails != NULL) {
3814                             free(gameInfo.resultDetails);
3815                             gameInfo.resultDetails = NULL;
3816                         }
3817                     }
3818                     HistorySet(parseList, backwardMostMove,
3819                                forwardMostMove, currentMove-1);
3820                     DisplayMove(currentMove - 1);
3821                     if (started == STARTED_MOVES) next_out = i;
3822                     started = STARTED_NONE;
3823                     ics_getting_history = H_FALSE;
3824                     break;
3825
3826                   case STARTED_OBSERVE:
3827                     started = STARTED_NONE;
3828                     SendToICS(ics_prefix);
3829                     SendToICS("refresh\n");
3830                     break;
3831
3832                   default:
3833                     break;
3834                 }
3835                 if(bookHit) { // [HGM] book: simulate book reply
3836                     static char bookMove[MSG_SIZ]; // a bit generous?
3837
3838                     programStats.nodes = programStats.depth = programStats.time =
3839                     programStats.score = programStats.got_only_move = 0;
3840                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3841
3842                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3843                     strcat(bookMove, bookHit);
3844                     HandleMachineMove(bookMove, &first);
3845                 }
3846                 continue;
3847             }
3848
3849             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3850                  started == STARTED_HOLDINGS ||
3851                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3852                 /* Accumulate characters in move list or board */
3853                 parse[parse_pos++] = buf[i];
3854             }
3855
3856             /* Start of game messages.  Mostly we detect start of game
3857                when the first board image arrives.  On some versions
3858                of the ICS, though, we need to do a "refresh" after starting
3859                to observe in order to get the current board right away. */
3860             if (looking_at(buf, &i, "Adding game * to observation list")) {
3861                 started = STARTED_OBSERVE;
3862                 continue;
3863             }
3864
3865             /* Handle auto-observe */
3866             if (appData.autoObserve &&
3867                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3868                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3869                 char *player;
3870                 /* Choose the player that was highlighted, if any. */
3871                 if (star_match[0][0] == '\033' ||
3872                     star_match[1][0] != '\033') {
3873                     player = star_match[0];
3874                 } else {
3875                     player = star_match[2];
3876                 }
3877                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3878                         ics_prefix, StripHighlightAndTitle(player));
3879                 SendToICS(str);
3880
3881                 /* Save ratings from notify string */
3882                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3883                 player1Rating = string_to_rating(star_match[1]);
3884                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3885                 player2Rating = string_to_rating(star_match[3]);
3886
3887                 if (appData.debugMode)
3888                   fprintf(debugFP,
3889                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3890                           player1Name, player1Rating,
3891                           player2Name, player2Rating);
3892
3893                 continue;
3894             }
3895
3896             /* Deal with automatic examine mode after a game,
3897                and with IcsObserving -> IcsExamining transition */
3898             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3899                 looking_at(buf, &i, "has made you an examiner of game *")) {
3900
3901                 int gamenum = atoi(star_match[0]);
3902                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3903                     gamenum == ics_gamenum) {
3904                     /* We were already playing or observing this game;
3905                        no need to refetch history */
3906                     gameMode = IcsExamining;
3907                     if (pausing) {
3908                         pauseExamForwardMostMove = forwardMostMove;
3909                     } else if (currentMove < forwardMostMove) {
3910                         ForwardInner(forwardMostMove);
3911                     }
3912                 } else {
3913                     /* I don't think this case really can happen */
3914                     SendToICS(ics_prefix);
3915                     SendToICS("refresh\n");
3916                 }
3917                 continue;
3918             }
3919
3920             /* Error messages */
3921 //          if (ics_user_moved) {
3922             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3923                 if (looking_at(buf, &i, "Illegal move") ||
3924                     looking_at(buf, &i, "Not a legal move") ||
3925                     looking_at(buf, &i, "Your king is in check") ||
3926                     looking_at(buf, &i, "It isn't your turn") ||
3927                     looking_at(buf, &i, "It is not your move")) {
3928                     /* Illegal move */
3929                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3930                         currentMove = forwardMostMove-1;
3931                         DisplayMove(currentMove - 1); /* before DMError */
3932                         DrawPosition(FALSE, boards[currentMove]);
3933                         SwitchClocks(forwardMostMove-1); // [HGM] race
3934                         DisplayBothClocks();
3935                     }
3936                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3937                     ics_user_moved = 0;
3938                     continue;
3939                 }
3940             }
3941
3942             if (looking_at(buf, &i, "still have time") ||
3943                 looking_at(buf, &i, "not out of time") ||
3944                 looking_at(buf, &i, "either player is out of time") ||
3945                 looking_at(buf, &i, "has timeseal; checking")) {
3946                 /* We must have called his flag a little too soon */
3947                 whiteFlag = blackFlag = FALSE;
3948                 continue;
3949             }
3950
3951             if (looking_at(buf, &i, "added * seconds to") ||
3952                 looking_at(buf, &i, "seconds were added to")) {
3953                 /* Update the clocks */
3954                 SendToICS(ics_prefix);
3955                 SendToICS("refresh\n");
3956                 continue;
3957             }
3958
3959             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3960                 ics_clock_paused = TRUE;
3961                 StopClocks();
3962                 continue;
3963             }
3964
3965             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3966                 ics_clock_paused = FALSE;
3967                 StartClocks();
3968                 continue;
3969             }
3970
3971             /* Grab player ratings from the Creating: message.
3972                Note we have to check for the special case when
3973                the ICS inserts things like [white] or [black]. */
3974             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3975                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3976                 /* star_matches:
3977                    0    player 1 name (not necessarily white)
3978                    1    player 1 rating
3979                    2    empty, white, or black (IGNORED)
3980                    3    player 2 name (not necessarily black)
3981                    4    player 2 rating
3982
3983                    The names/ratings are sorted out when the game
3984                    actually starts (below).
3985                 */
3986                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3987                 player1Rating = string_to_rating(star_match[1]);
3988                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3989                 player2Rating = string_to_rating(star_match[4]);
3990
3991                 if (appData.debugMode)
3992                   fprintf(debugFP,
3993                           "Ratings from 'Creating:' %s %d, %s %d\n",
3994                           player1Name, player1Rating,
3995                           player2Name, player2Rating);
3996
3997                 continue;
3998             }
3999
4000             /* Improved generic start/end-of-game messages */
4001             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4002                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4003                 /* If tkind == 0: */
4004                 /* star_match[0] is the game number */
4005                 /*           [1] is the white player's name */
4006                 /*           [2] is the black player's name */
4007                 /* For end-of-game: */
4008                 /*           [3] is the reason for the game end */
4009                 /*           [4] is a PGN end game-token, preceded by " " */
4010                 /* For start-of-game: */
4011                 /*           [3] begins with "Creating" or "Continuing" */
4012                 /*           [4] is " *" or empty (don't care). */
4013                 int gamenum = atoi(star_match[0]);
4014                 char *whitename, *blackname, *why, *endtoken;
4015                 ChessMove endtype = EndOfFile;
4016
4017                 if (tkind == 0) {
4018                   whitename = star_match[1];
4019                   blackname = star_match[2];
4020                   why = star_match[3];
4021                   endtoken = star_match[4];
4022                 } else {
4023                   whitename = star_match[1];
4024                   blackname = star_match[3];
4025                   why = star_match[5];
4026                   endtoken = star_match[6];
4027                 }
4028
4029                 /* Game start messages */
4030                 if (strncmp(why, "Creating ", 9) == 0 ||
4031                     strncmp(why, "Continuing ", 11) == 0) {
4032                     gs_gamenum = gamenum;
4033                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4034                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4035                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4036 #if ZIPPY
4037                     if (appData.zippyPlay) {
4038                         ZippyGameStart(whitename, blackname);
4039                     }
4040 #endif /*ZIPPY*/
4041                     partnerBoardValid = FALSE; // [HGM] bughouse
4042                     continue;
4043                 }
4044
4045                 /* Game end messages */
4046                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4047                     ics_gamenum != gamenum) {
4048                     continue;
4049                 }
4050                 while (endtoken[0] == ' ') endtoken++;
4051                 switch (endtoken[0]) {
4052                   case '*':
4053                   default:
4054                     endtype = GameUnfinished;
4055                     break;
4056                   case '0':
4057                     endtype = BlackWins;
4058                     break;
4059                   case '1':
4060                     if (endtoken[1] == '/')
4061                       endtype = GameIsDrawn;
4062                     else
4063                       endtype = WhiteWins;
4064                     break;
4065                 }
4066                 GameEnds(endtype, why, GE_ICS);
4067 #if ZIPPY
4068                 if (appData.zippyPlay && first.initDone) {
4069                     ZippyGameEnd(endtype, why);
4070                     if (first.pr == NoProc) {
4071                       /* Start the next process early so that we'll
4072                          be ready for the next challenge */
4073                       StartChessProgram(&first);
4074                     }
4075                     /* Send "new" early, in case this command takes
4076                        a long time to finish, so that we'll be ready
4077                        for the next challenge. */
4078                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4079                     Reset(TRUE, TRUE);
4080                 }
4081 #endif /*ZIPPY*/
4082                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4083                 continue;
4084             }
4085
4086             if (looking_at(buf, &i, "Removing game * from observation") ||
4087                 looking_at(buf, &i, "no longer observing game *") ||
4088                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4089                 if (gameMode == IcsObserving &&
4090                     atoi(star_match[0]) == ics_gamenum)
4091                   {
4092                       /* icsEngineAnalyze */
4093                       if (appData.icsEngineAnalyze) {
4094                             ExitAnalyzeMode();
4095                             ModeHighlight();
4096                       }
4097                       StopClocks();
4098                       gameMode = IcsIdle;
4099                       ics_gamenum = -1;
4100                       ics_user_moved = FALSE;
4101                   }
4102                 continue;
4103             }
4104
4105             if (looking_at(buf, &i, "no longer examining game *")) {
4106                 if (gameMode == IcsExamining &&
4107                     atoi(star_match[0]) == ics_gamenum)
4108                   {
4109                       gameMode = IcsIdle;
4110                       ics_gamenum = -1;
4111                       ics_user_moved = FALSE;
4112                   }
4113                 continue;
4114             }
4115
4116             /* Advance leftover_start past any newlines we find,
4117                so only partial lines can get reparsed */
4118             if (looking_at(buf, &i, "\n")) {
4119                 prevColor = curColor;
4120                 if (curColor != ColorNormal) {
4121                     if (oldi > next_out) {
4122                         SendToPlayer(&buf[next_out], oldi - next_out);
4123                         next_out = oldi;
4124                     }
4125                     Colorize(ColorNormal, FALSE);
4126                     curColor = ColorNormal;
4127                 }
4128                 if (started == STARTED_BOARD) {
4129                     started = STARTED_NONE;
4130                     parse[parse_pos] = NULLCHAR;
4131                     ParseBoard12(parse);
4132                     ics_user_moved = 0;
4133
4134                     /* Send premove here */
4135                     if (appData.premove) {
4136                       char str[MSG_SIZ];
4137                       if (currentMove == 0 &&
4138                           gameMode == IcsPlayingWhite &&
4139                           appData.premoveWhite) {
4140                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4141                         if (appData.debugMode)
4142                           fprintf(debugFP, "Sending premove:\n");
4143                         SendToICS(str);
4144                       } else if (currentMove == 1 &&
4145                                  gameMode == IcsPlayingBlack &&
4146                                  appData.premoveBlack) {
4147                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4148                         if (appData.debugMode)
4149                           fprintf(debugFP, "Sending premove:\n");
4150                         SendToICS(str);
4151                       } else if (gotPremove) {
4152                         gotPremove = 0;
4153                         ClearPremoveHighlights();
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                           UserMoveEvent(premoveFromX, premoveFromY,
4157                                         premoveToX, premoveToY,
4158                                         premovePromoChar);
4159                       }
4160                     }
4161
4162                     /* Usually suppress following prompt */
4163                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4164                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4165                         if (looking_at(buf, &i, "*% ")) {
4166                             savingComment = FALSE;
4167                             suppressKibitz = 0;
4168                         }
4169                     }
4170                     next_out = i;
4171                 } else if (started == STARTED_HOLDINGS) {
4172                     int gamenum;
4173                     char new_piece[MSG_SIZ];
4174                     started = STARTED_NONE;
4175                     parse[parse_pos] = NULLCHAR;
4176                     if (appData.debugMode)
4177                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4178                                                         parse, currentMove);
4179                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4180                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4181                         if (gameInfo.variant == VariantNormal) {
4182                           /* [HGM] We seem to switch variant during a game!
4183                            * Presumably no holdings were displayed, so we have
4184                            * to move the position two files to the right to
4185                            * create room for them!
4186                            */
4187                           VariantClass newVariant;
4188                           switch(gameInfo.boardWidth) { // base guess on board width
4189                                 case 9:  newVariant = VariantShogi; break;
4190                                 case 10: newVariant = VariantGreat; break;
4191                                 default: newVariant = VariantCrazyhouse; break;
4192                           }
4193                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4194                           /* Get a move list just to see the header, which
4195                              will tell us whether this is really bug or zh */
4196                           if (ics_getting_history == H_FALSE) {
4197                             ics_getting_history = H_REQUESTED;
4198                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4199                             SendToICS(str);
4200                           }
4201                         }
4202                         new_piece[0] = NULLCHAR;
4203                         sscanf(parse, "game %d white [%s black [%s <- %s",
4204                                &gamenum, white_holding, black_holding,
4205                                new_piece);
4206                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4207                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4208                         /* [HGM] copy holdings to board holdings area */
4209                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4210                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4211                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4212 #if ZIPPY
4213                         if (appData.zippyPlay && first.initDone) {
4214                             ZippyHoldings(white_holding, black_holding,
4215                                           new_piece);
4216                         }
4217 #endif /*ZIPPY*/
4218                         if (tinyLayout || smallLayout) {
4219                             char wh[16], bh[16];
4220                             PackHolding(wh, white_holding);
4221                             PackHolding(bh, black_holding);
4222                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4223                                     gameInfo.white, gameInfo.black);
4224                         } else {
4225                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4226                                     gameInfo.white, white_holding, _("vs."),
4227                                     gameInfo.black, black_holding);
4228                         }
4229                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4230                         DrawPosition(FALSE, boards[currentMove]);
4231                         DisplayTitle(str);
4232                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
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 partner-board holdings area */
4239                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4240                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4241                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4242                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4243                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4244                       }
4245                     }
4246                     /* Suppress following prompt */
4247                     if (looking_at(buf, &i, "*% ")) {
4248                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4249                         savingComment = FALSE;
4250                         suppressKibitz = 0;
4251                     }
4252                     next_out = i;
4253                 }
4254                 continue;
4255             }
4256
4257             i++;                /* skip unparsed character and loop back */
4258         }
4259
4260         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4261 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4262 //          SendToPlayer(&buf[next_out], i - next_out);
4263             started != STARTED_HOLDINGS && leftover_start > next_out) {
4264             SendToPlayer(&buf[next_out], leftover_start - next_out);
4265             next_out = i;
4266         }
4267
4268         leftover_len = buf_len - leftover_start;
4269         /* if buffer ends with something we couldn't parse,
4270            reparse it after appending the next read */
4271
4272     } else if (count == 0) {
4273         RemoveInputSource(isr);
4274         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4275     } else {
4276         DisplayFatalError(_("Error reading from ICS"), error, 1);
4277     }
4278 }
4279
4280
4281 /* Board style 12 looks like this:
4282
4283    <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
4284
4285  * The "<12> " is stripped before it gets to this routine.  The two
4286  * trailing 0's (flip state and clock ticking) are later addition, and
4287  * some chess servers may not have them, or may have only the first.
4288  * Additional trailing fields may be added in the future.
4289  */
4290
4291 #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"
4292
4293 #define RELATION_OBSERVING_PLAYED    0
4294 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4295 #define RELATION_PLAYING_MYMOVE      1
4296 #define RELATION_PLAYING_NOTMYMOVE  -1
4297 #define RELATION_EXAMINING           2
4298 #define RELATION_ISOLATED_BOARD     -3
4299 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4300
4301 void
4302 ParseBoard12 (char *string)
4303 {
4304 #if ZIPPY
4305     int i, takeback;
4306     char *bookHit = NULL; // [HGM] book
4307 #endif
4308     GameMode newGameMode;
4309     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4310     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4311     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4312     char to_play, board_chars[200];
4313     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4314     char black[32], white[32];
4315     Board board;
4316     int prevMove = currentMove;
4317     int ticking = 2;
4318     ChessMove moveType;
4319     int fromX, fromY, toX, toY;
4320     char promoChar;
4321     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4322     Boolean weird = FALSE, reqFlag = FALSE;
4323
4324     fromX = fromY = toX = toY = -1;
4325
4326     newGame = FALSE;
4327
4328     if (appData.debugMode)
4329       fprintf(debugFP, "Parsing board: %s\n", string);
4330
4331     move_str[0] = NULLCHAR;
4332     elapsed_time[0] = NULLCHAR;
4333     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4334         int  i = 0, j;
4335         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4336             if(string[i] == ' ') { ranks++; files = 0; }
4337             else files++;
4338             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4339             i++;
4340         }
4341         for(j = 0; j <i; j++) board_chars[j] = string[j];
4342         board_chars[i] = '\0';
4343         string += i + 1;
4344     }
4345     n = sscanf(string, PATTERN, &to_play, &double_push,
4346                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4347                &gamenum, white, black, &relation, &basetime, &increment,
4348                &white_stren, &black_stren, &white_time, &black_time,
4349                &moveNum, str, elapsed_time, move_str, &ics_flip,
4350                &ticking);
4351
4352     if (n < 21) {
4353         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4354         DisplayError(str, 0);
4355         return;
4356     }
4357
4358     /* Convert the move number to internal form */
4359     moveNum = (moveNum - 1) * 2;
4360     if (to_play == 'B') moveNum++;
4361     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4362       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4363                         0, 1);
4364       return;
4365     }
4366
4367     switch (relation) {
4368       case RELATION_OBSERVING_PLAYED:
4369       case RELATION_OBSERVING_STATIC:
4370         if (gamenum == -1) {
4371             /* Old ICC buglet */
4372             relation = RELATION_OBSERVING_STATIC;
4373         }
4374         newGameMode = IcsObserving;
4375         break;
4376       case RELATION_PLAYING_MYMOVE:
4377       case RELATION_PLAYING_NOTMYMOVE:
4378         newGameMode =
4379           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4380             IcsPlayingWhite : IcsPlayingBlack;
4381         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4382         break;
4383       case RELATION_EXAMINING:
4384         newGameMode = IcsExamining;
4385         break;
4386       case RELATION_ISOLATED_BOARD:
4387       default:
4388         /* Just display this board.  If user was doing something else,
4389            we will forget about it until the next board comes. */
4390         newGameMode = IcsIdle;
4391         break;
4392       case RELATION_STARTING_POSITION:
4393         newGameMode = gameMode;
4394         break;
4395     }
4396
4397     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4398         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4399          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4400       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4401       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4402       static int lastBgGame = -1;
4403       char *toSqr;
4404       for (k = 0; k < ranks; k++) {
4405         for (j = 0; j < files; j++)
4406           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4407         if(gameInfo.holdingsWidth > 1) {
4408              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4409              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4410         }
4411       }
4412       CopyBoard(partnerBoard, board);
4413       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4414         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4415         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4416       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4417       if(toSqr = strchr(str, '-')) {
4418         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4419         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4420       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4421       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4422       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4423       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4424       if(twoBoards) {
4425           DisplayWhiteClock(white_time*fac, to_play == 'W');
4426           DisplayBlackClock(black_time*fac, to_play != 'W');
4427           activePartner = to_play;
4428           if(gamenum != lastBgGame) {
4429               char buf[MSG_SIZ];
4430               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4431               DisplayTitle(buf);
4432           }
4433           lastBgGame = gamenum;
4434           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4435                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4436       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4437                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4438       if(!twoBoards) DisplayMessage(partnerStatus, "");
4439         partnerBoardValid = TRUE;
4440       return;
4441     }
4442
4443     if(appData.dualBoard && appData.bgObserve) {
4444         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4445             SendToICS(ics_prefix), SendToICS("pobserve\n");
4446         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4447             char buf[MSG_SIZ];
4448             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4449             SendToICS(buf);
4450         }
4451     }
4452
4453     /* Modify behavior for initial board display on move listing
4454        of wild games.
4455        */
4456     switch (ics_getting_history) {
4457       case H_FALSE:
4458       case H_REQUESTED:
4459         break;
4460       case H_GOT_REQ_HEADER:
4461       case H_GOT_UNREQ_HEADER:
4462         /* This is the initial position of the current game */
4463         gamenum = ics_gamenum;
4464         moveNum = 0;            /* old ICS bug workaround */
4465         if (to_play == 'B') {
4466           startedFromSetupPosition = TRUE;
4467           blackPlaysFirst = TRUE;
4468           moveNum = 1;
4469           if (forwardMostMove == 0) forwardMostMove = 1;
4470           if (backwardMostMove == 0) backwardMostMove = 1;
4471           if (currentMove == 0) currentMove = 1;
4472         }
4473         newGameMode = gameMode;
4474         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4475         break;
4476       case H_GOT_UNWANTED_HEADER:
4477         /* This is an initial board that we don't want */
4478         return;
4479       case H_GETTING_MOVES:
4480         /* Should not happen */
4481         DisplayError(_("Error gathering move list: extra board"), 0);
4482         ics_getting_history = H_FALSE;
4483         return;
4484     }
4485
4486    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4487                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4488                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4489      /* [HGM] We seem to have switched variant unexpectedly
4490       * Try to guess new variant from board size
4491       */
4492           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4493           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4494           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4495           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4496           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4497           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4498           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4499           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4500           /* Get a move list just to see the header, which
4501              will tell us whether this is really bug or zh */
4502           if (ics_getting_history == H_FALSE) {
4503             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4504             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4505             SendToICS(str);
4506           }
4507     }
4508
4509     /* Take action if this is the first board of a new game, or of a
4510        different game than is currently being displayed.  */
4511     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4512         relation == RELATION_ISOLATED_BOARD) {
4513
4514         /* Forget the old game and get the history (if any) of the new one */
4515         if (gameMode != BeginningOfGame) {
4516           Reset(TRUE, TRUE);
4517         }
4518         newGame = TRUE;
4519         if (appData.autoRaiseBoard) BoardToTop();
4520         prevMove = -3;
4521         if (gamenum == -1) {
4522             newGameMode = IcsIdle;
4523         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4524                    appData.getMoveList && !reqFlag) {
4525             /* Need to get game history */
4526             ics_getting_history = H_REQUESTED;
4527             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4528             SendToICS(str);
4529         }
4530
4531         /* Initially flip the board to have black on the bottom if playing
4532            black or if the ICS flip flag is set, but let the user change
4533            it with the Flip View button. */
4534         flipView = appData.autoFlipView ?
4535           (newGameMode == IcsPlayingBlack) || ics_flip :
4536           appData.flipView;
4537
4538         /* Done with values from previous mode; copy in new ones */
4539         gameMode = newGameMode;
4540         ModeHighlight();
4541         ics_gamenum = gamenum;
4542         if (gamenum == gs_gamenum) {
4543             int klen = strlen(gs_kind);
4544             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4545             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4546             gameInfo.event = StrSave(str);
4547         } else {
4548             gameInfo.event = StrSave("ICS game");
4549         }
4550         gameInfo.site = StrSave(appData.icsHost);
4551         gameInfo.date = PGNDate();
4552         gameInfo.round = StrSave("-");
4553         gameInfo.white = StrSave(white);
4554         gameInfo.black = StrSave(black);
4555         timeControl = basetime * 60 * 1000;
4556         timeControl_2 = 0;
4557         timeIncrement = increment * 1000;
4558         movesPerSession = 0;
4559         gameInfo.timeControl = TimeControlTagValue();
4560         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4561   if (appData.debugMode) {
4562     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4563     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4564     setbuf(debugFP, NULL);
4565   }
4566
4567         gameInfo.outOfBook = NULL;
4568
4569         /* Do we have the ratings? */
4570         if (strcmp(player1Name, white) == 0 &&
4571             strcmp(player2Name, black) == 0) {
4572             if (appData.debugMode)
4573               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4574                       player1Rating, player2Rating);
4575             gameInfo.whiteRating = player1Rating;
4576             gameInfo.blackRating = player2Rating;
4577         } else if (strcmp(player2Name, white) == 0 &&
4578                    strcmp(player1Name, black) == 0) {
4579             if (appData.debugMode)
4580               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4581                       player2Rating, player1Rating);
4582             gameInfo.whiteRating = player2Rating;
4583             gameInfo.blackRating = player1Rating;
4584         }
4585         player1Name[0] = player2Name[0] = NULLCHAR;
4586
4587         /* Silence shouts if requested */
4588         if (appData.quietPlay &&
4589             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4590             SendToICS(ics_prefix);
4591             SendToICS("set shout 0\n");
4592         }
4593     }
4594
4595     /* Deal with midgame name changes */
4596     if (!newGame) {
4597         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4598             if (gameInfo.white) free(gameInfo.white);
4599             gameInfo.white = StrSave(white);
4600         }
4601         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4602             if (gameInfo.black) free(gameInfo.black);
4603             gameInfo.black = StrSave(black);
4604         }
4605     }
4606
4607     /* Throw away game result if anything actually changes in examine mode */
4608     if (gameMode == IcsExamining && !newGame) {
4609         gameInfo.result = GameUnfinished;
4610         if (gameInfo.resultDetails != NULL) {
4611             free(gameInfo.resultDetails);
4612             gameInfo.resultDetails = NULL;
4613         }
4614     }
4615
4616     /* In pausing && IcsExamining mode, we ignore boards coming
4617        in if they are in a different variation than we are. */
4618     if (pauseExamInvalid) return;
4619     if (pausing && gameMode == IcsExamining) {
4620         if (moveNum <= pauseExamForwardMostMove) {
4621             pauseExamInvalid = TRUE;
4622             forwardMostMove = pauseExamForwardMostMove;
4623             return;
4624         }
4625     }
4626
4627   if (appData.debugMode) {
4628     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4629   }
4630     /* Parse the board */
4631     for (k = 0; k < ranks; k++) {
4632       for (j = 0; j < files; j++)
4633         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4634       if(gameInfo.holdingsWidth > 1) {
4635            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4636            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4637       }
4638     }
4639     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4640       board[5][BOARD_RGHT+1] = WhiteAngel;
4641       board[6][BOARD_RGHT+1] = WhiteMarshall;
4642       board[1][0] = BlackMarshall;
4643       board[2][0] = BlackAngel;
4644       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4645     }
4646     CopyBoard(boards[moveNum], board);
4647     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4648     if (moveNum == 0) {
4649         startedFromSetupPosition =
4650           !CompareBoards(board, initialPosition);
4651         if(startedFromSetupPosition)
4652             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4653     }
4654
4655     /* [HGM] Set castling rights. Take the outermost Rooks,
4656        to make it also work for FRC opening positions. Note that board12
4657        is really defective for later FRC positions, as it has no way to
4658        indicate which Rook can castle if they are on the same side of King.
4659        For the initial position we grant rights to the outermost Rooks,
4660        and remember thos rights, and we then copy them on positions
4661        later in an FRC game. This means WB might not recognize castlings with
4662        Rooks that have moved back to their original position as illegal,
4663        but in ICS mode that is not its job anyway.
4664     */
4665     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4666     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4667
4668         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4669             if(board[0][i] == WhiteRook) j = i;
4670         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4671         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4672             if(board[0][i] == WhiteRook) j = i;
4673         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4674         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4675             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4676         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4677         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4678             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4679         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4680
4681         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4682         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4683         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4684             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4685         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4686             if(board[BOARD_HEIGHT-1][k] == bKing)
4687                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4688         if(gameInfo.variant == VariantTwoKings) {
4689             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4690             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4691             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4692         }
4693     } else { int r;
4694         r = boards[moveNum][CASTLING][0] = initialRights[0];
4695         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4696         r = boards[moveNum][CASTLING][1] = initialRights[1];
4697         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4698         r = boards[moveNum][CASTLING][3] = initialRights[3];
4699         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4700         r = boards[moveNum][CASTLING][4] = initialRights[4];
4701         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4702         /* wildcastle kludge: always assume King has rights */
4703         r = boards[moveNum][CASTLING][2] = initialRights[2];
4704         r = boards[moveNum][CASTLING][5] = initialRights[5];
4705     }
4706     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4707     boards[moveNum][EP_STATUS] = EP_NONE;
4708     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4709     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4710     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4711
4712
4713     if (ics_getting_history == H_GOT_REQ_HEADER ||
4714         ics_getting_history == H_GOT_UNREQ_HEADER) {
4715         /* This was an initial position from a move list, not
4716            the current position */
4717         return;
4718     }
4719
4720     /* Update currentMove and known move number limits */
4721     newMove = newGame || moveNum > forwardMostMove;
4722
4723     if (newGame) {
4724         forwardMostMove = backwardMostMove = currentMove = moveNum;
4725         if (gameMode == IcsExamining && moveNum == 0) {
4726           /* Workaround for ICS limitation: we are not told the wild
4727              type when starting to examine a game.  But if we ask for
4728              the move list, the move list header will tell us */
4729             ics_getting_history = H_REQUESTED;
4730             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4731             SendToICS(str);
4732         }
4733     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4734                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4735 #if ZIPPY
4736         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4737         /* [HGM] applied this also to an engine that is silently watching        */
4738         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4739             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4740             gameInfo.variant == currentlyInitializedVariant) {
4741           takeback = forwardMostMove - moveNum;
4742           for (i = 0; i < takeback; i++) {
4743             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4744             SendToProgram("undo\n", &first);
4745           }
4746         }
4747 #endif
4748
4749         forwardMostMove = moveNum;
4750         if (!pausing || currentMove > forwardMostMove)
4751           currentMove = forwardMostMove;
4752     } else {
4753         /* New part of history that is not contiguous with old part */
4754         if (pausing && gameMode == IcsExamining) {
4755             pauseExamInvalid = TRUE;
4756             forwardMostMove = pauseExamForwardMostMove;
4757             return;
4758         }
4759         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4760 #if ZIPPY
4761             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4762                 // [HGM] when we will receive the move list we now request, it will be
4763                 // fed to the engine from the first move on. So if the engine is not
4764                 // in the initial position now, bring it there.
4765                 InitChessProgram(&first, 0);
4766             }
4767 #endif
4768             ics_getting_history = H_REQUESTED;
4769             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4770             SendToICS(str);
4771         }
4772         forwardMostMove = backwardMostMove = currentMove = moveNum;
4773     }
4774
4775     /* Update the clocks */
4776     if (strchr(elapsed_time, '.')) {
4777       /* Time is in ms */
4778       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4779       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4780     } else {
4781       /* Time is in seconds */
4782       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4783       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4784     }
4785
4786
4787 #if ZIPPY
4788     if (appData.zippyPlay && newGame &&
4789         gameMode != IcsObserving && gameMode != IcsIdle &&
4790         gameMode != IcsExamining)
4791       ZippyFirstBoard(moveNum, basetime, increment);
4792 #endif
4793
4794     /* Put the move on the move list, first converting
4795        to canonical algebraic form. */
4796     if (moveNum > 0) {
4797   if (appData.debugMode) {
4798     int f = forwardMostMove;
4799     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4800             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4801             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4802     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4803     fprintf(debugFP, "moveNum = %d\n", moveNum);
4804     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4805     setbuf(debugFP, NULL);
4806   }
4807         if (moveNum <= backwardMostMove) {
4808             /* We don't know what the board looked like before
4809                this move.  Punt. */
4810           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4811             strcat(parseList[moveNum - 1], " ");
4812             strcat(parseList[moveNum - 1], elapsed_time);
4813             moveList[moveNum - 1][0] = NULLCHAR;
4814         } else if (strcmp(move_str, "none") == 0) {
4815             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4816             /* Again, we don't know what the board looked like;
4817                this is really the start of the game. */
4818             parseList[moveNum - 1][0] = NULLCHAR;
4819             moveList[moveNum - 1][0] = NULLCHAR;
4820             backwardMostMove = moveNum;
4821             startedFromSetupPosition = TRUE;
4822             fromX = fromY = toX = toY = -1;
4823         } else {
4824           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4825           //                 So we parse the long-algebraic move string in stead of the SAN move
4826           int valid; char buf[MSG_SIZ], *prom;
4827
4828           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4829                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4830           // str looks something like "Q/a1-a2"; kill the slash
4831           if(str[1] == '/')
4832             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4833           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4834           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4835                 strcat(buf, prom); // long move lacks promo specification!
4836           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4837                 if(appData.debugMode)
4838                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4839                 safeStrCpy(move_str, buf, MSG_SIZ);
4840           }
4841           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4842                                 &fromX, &fromY, &toX, &toY, &promoChar)
4843                || ParseOneMove(buf, moveNum - 1, &moveType,
4844                                 &fromX, &fromY, &toX, &toY, &promoChar);
4845           // end of long SAN patch
4846           if (valid) {
4847             (void) CoordsToAlgebraic(boards[moveNum - 1],
4848                                      PosFlags(moveNum - 1),
4849                                      fromY, fromX, toY, toX, promoChar,
4850                                      parseList[moveNum-1]);
4851             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4852               case MT_NONE:
4853               case MT_STALEMATE:
4854               default:
4855                 break;
4856               case MT_CHECK:
4857                 if(!IS_SHOGI(gameInfo.variant))
4858                     strcat(parseList[moveNum - 1], "+");
4859                 break;
4860               case MT_CHECKMATE:
4861               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4862                 strcat(parseList[moveNum - 1], "#");
4863                 break;
4864             }
4865             strcat(parseList[moveNum - 1], " ");
4866             strcat(parseList[moveNum - 1], elapsed_time);
4867             /* currentMoveString is set as a side-effect of ParseOneMove */
4868             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4869             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4870             strcat(moveList[moveNum - 1], "\n");
4871
4872             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4873                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4874               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4875                 ChessSquare old, new = boards[moveNum][k][j];
4876                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4877                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4878                   if(old == new) continue;
4879                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4880                   else if(new == WhiteWazir || new == BlackWazir) {
4881                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4882                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4883                       else boards[moveNum][k][j] = old; // preserve type of Gold
4884                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4885                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4886               }
4887           } else {
4888             /* Move from ICS was illegal!?  Punt. */
4889             if (appData.debugMode) {
4890               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4891               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4892             }
4893             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4894             strcat(parseList[moveNum - 1], " ");
4895             strcat(parseList[moveNum - 1], elapsed_time);
4896             moveList[moveNum - 1][0] = NULLCHAR;
4897             fromX = fromY = toX = toY = -1;
4898           }
4899         }
4900   if (appData.debugMode) {
4901     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4902     setbuf(debugFP, NULL);
4903   }
4904
4905 #if ZIPPY
4906         /* Send move to chess program (BEFORE animating it). */
4907         if (appData.zippyPlay && !newGame && newMove &&
4908            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4909
4910             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4911                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4912                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4913                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4914                             move_str);
4915                     DisplayError(str, 0);
4916                 } else {
4917                     if (first.sendTime) {
4918                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4919                     }
4920                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4921                     if (firstMove && !bookHit) {
4922                         firstMove = FALSE;
4923                         if (first.useColors) {
4924                           SendToProgram(gameMode == IcsPlayingWhite ?
4925                                         "white\ngo\n" :
4926                                         "black\ngo\n", &first);
4927                         } else {
4928                           SendToProgram("go\n", &first);
4929                         }
4930                         first.maybeThinking = TRUE;
4931                     }
4932                 }
4933             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4934               if (moveList[moveNum - 1][0] == NULLCHAR) {
4935                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4936                 DisplayError(str, 0);
4937               } else {
4938                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4939                 SendMoveToProgram(moveNum - 1, &first);
4940               }
4941             }
4942         }
4943 #endif
4944     }
4945
4946     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4947         /* If move comes from a remote source, animate it.  If it
4948            isn't remote, it will have already been animated. */
4949         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4950             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4951         }
4952         if (!pausing && appData.highlightLastMove) {
4953             SetHighlights(fromX, fromY, toX, toY);
4954         }
4955     }
4956
4957     /* Start the clocks */
4958     whiteFlag = blackFlag = FALSE;
4959     appData.clockMode = !(basetime == 0 && increment == 0);
4960     if (ticking == 0) {
4961       ics_clock_paused = TRUE;
4962       StopClocks();
4963     } else if (ticking == 1) {
4964       ics_clock_paused = FALSE;
4965     }
4966     if (gameMode == IcsIdle ||
4967         relation == RELATION_OBSERVING_STATIC ||
4968         relation == RELATION_EXAMINING ||
4969         ics_clock_paused)
4970       DisplayBothClocks();
4971     else
4972       StartClocks();
4973
4974     /* Display opponents and material strengths */
4975     if (gameInfo.variant != VariantBughouse &&
4976         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4977         if (tinyLayout || smallLayout) {
4978             if(gameInfo.variant == VariantNormal)
4979               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4981                     basetime, increment);
4982             else
4983               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4984                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4985                     basetime, increment, (int) gameInfo.variant);
4986         } else {
4987             if(gameInfo.variant == VariantNormal)
4988               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4989                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4990                     basetime, increment);
4991             else
4992               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4993                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4994                     basetime, increment, VariantName(gameInfo.variant));
4995         }
4996         DisplayTitle(str);
4997   if (appData.debugMode) {
4998     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4999   }
5000     }
5001
5002
5003     /* Display the board */
5004     if (!pausing && !appData.noGUI) {
5005
5006       if (appData.premove)
5007           if (!gotPremove ||
5008              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5009              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5010               ClearPremoveHighlights();
5011
5012       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5013         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5014       DrawPosition(j, boards[currentMove]);
5015
5016       DisplayMove(moveNum - 1);
5017       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5018             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5019               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5020         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5021       }
5022     }
5023
5024     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5025 #if ZIPPY
5026     if(bookHit) { // [HGM] book: simulate book reply
5027         static char bookMove[MSG_SIZ]; // a bit generous?
5028
5029         programStats.nodes = programStats.depth = programStats.time =
5030         programStats.score = programStats.got_only_move = 0;
5031         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5032
5033         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5034         strcat(bookMove, bookHit);
5035         HandleMachineMove(bookMove, &first);
5036     }
5037 #endif
5038 }
5039
5040 void
5041 GetMoveListEvent ()
5042 {
5043     char buf[MSG_SIZ];
5044     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5045         ics_getting_history = H_REQUESTED;
5046         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5047         SendToICS(buf);
5048     }
5049 }
5050
5051 void
5052 SendToBoth (char *msg)
5053 {   // to make it easy to keep two engines in step in dual analysis
5054     SendToProgram(msg, &first);
5055     if(second.analyzing) SendToProgram(msg, &second);
5056 }
5057
5058 void
5059 AnalysisPeriodicEvent (int force)
5060 {
5061     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5062          && !force) || !appData.periodicUpdates)
5063       return;
5064
5065     /* Send . command to Crafty to collect stats */
5066     SendToBoth(".\n");
5067
5068     /* Don't send another until we get a response (this makes
5069        us stop sending to old Crafty's which don't understand
5070        the "." command (sending illegal cmds resets node count & time,
5071        which looks bad)) */
5072     programStats.ok_to_send = 0;
5073 }
5074
5075 void
5076 ics_update_width (int new_width)
5077 {
5078         ics_printf("set width %d\n", new_width);
5079 }
5080
5081 void
5082 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5083 {
5084     char buf[MSG_SIZ];
5085
5086     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5087         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5088             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5089             SendToProgram(buf, cps);
5090             return;
5091         }
5092         // null move in variant where engine does not understand it (for analysis purposes)
5093         SendBoard(cps, moveNum + 1); // send position after move in stead.
5094         return;
5095     }
5096     if (cps->useUsermove) {
5097       SendToProgram("usermove ", cps);
5098     }
5099     if (cps->useSAN) {
5100       char *space;
5101       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5102         int len = space - parseList[moveNum];
5103         memcpy(buf, parseList[moveNum], len);
5104         buf[len++] = '\n';
5105         buf[len] = NULLCHAR;
5106       } else {
5107         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5108       }
5109       SendToProgram(buf, cps);
5110     } else {
5111       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5112         AlphaRank(moveList[moveNum], 4);
5113         SendToProgram(moveList[moveNum], cps);
5114         AlphaRank(moveList[moveNum], 4); // and back
5115       } else
5116       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5117        * the engine. It would be nice to have a better way to identify castle
5118        * moves here. */
5119       if(appData.fischerCastling && cps->useOOCastle) {
5120         int fromX = moveList[moveNum][0] - AAA;
5121         int fromY = moveList[moveNum][1] - ONE;
5122         int toX = moveList[moveNum][2] - AAA;
5123         int toY = moveList[moveNum][3] - ONE;
5124         if((boards[moveNum][fromY][fromX] == WhiteKing
5125             && boards[moveNum][toY][toX] == WhiteRook)
5126            || (boards[moveNum][fromY][fromX] == BlackKing
5127                && boards[moveNum][toY][toX] == BlackRook)) {
5128           if(toX > fromX) SendToProgram("O-O\n", cps);
5129           else SendToProgram("O-O-O\n", cps);
5130         }
5131         else SendToProgram(moveList[moveNum], cps);
5132       } else
5133       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5134           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5135                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5136                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5137                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5138           SendToProgram(buf, cps);
5139       } else
5140       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5141         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5142           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5143           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5144                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5145         } else
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5147                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5148         SendToProgram(buf, cps);
5149       }
5150       else SendToProgram(moveList[moveNum], cps);
5151       /* End of additions by Tord */
5152     }
5153
5154     /* [HGM] setting up the opening has brought engine in force mode! */
5155     /*       Send 'go' if we are in a mode where machine should play. */
5156     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5157         (gameMode == TwoMachinesPlay   ||
5158 #if ZIPPY
5159          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5160 #endif
5161          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5162         SendToProgram("go\n", cps);
5163   if (appData.debugMode) {
5164     fprintf(debugFP, "(extra)\n");
5165   }
5166     }
5167     setboardSpoiledMachineBlack = 0;
5168 }
5169
5170 void
5171 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5172 {
5173     char user_move[MSG_SIZ];
5174     char suffix[4];
5175
5176     if(gameInfo.variant == VariantSChess && promoChar) {
5177         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5178         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5179     } else suffix[0] = NULLCHAR;
5180
5181     switch (moveType) {
5182       default:
5183         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5184                 (int)moveType, fromX, fromY, toX, toY);
5185         DisplayError(user_move + strlen("say "), 0);
5186         break;
5187       case WhiteKingSideCastle:
5188       case BlackKingSideCastle:
5189       case WhiteQueenSideCastleWild:
5190       case BlackQueenSideCastleWild:
5191       /* PUSH Fabien */
5192       case WhiteHSideCastleFR:
5193       case BlackHSideCastleFR:
5194       /* POP Fabien */
5195         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5196         break;
5197       case WhiteQueenSideCastle:
5198       case BlackQueenSideCastle:
5199       case WhiteKingSideCastleWild:
5200       case BlackKingSideCastleWild:
5201       /* PUSH Fabien */
5202       case WhiteASideCastleFR:
5203       case BlackASideCastleFR:
5204       /* POP Fabien */
5205         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5206         break;
5207       case WhiteNonPromotion:
5208       case BlackNonPromotion:
5209         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5210         break;
5211       case WhitePromotion:
5212       case BlackPromotion:
5213         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5214            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5215           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5216                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5217                 PieceToChar(WhiteFerz));
5218         else if(gameInfo.variant == VariantGreat)
5219           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5220                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5221                 PieceToChar(WhiteMan));
5222         else
5223           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5224                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5225                 promoChar);
5226         break;
5227       case WhiteDrop:
5228       case BlackDrop:
5229       drop:
5230         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5231                  ToUpper(PieceToChar((ChessSquare) fromX)),
5232                  AAA + toX, ONE + toY);
5233         break;
5234       case IllegalMove:  /* could be a variant we don't quite understand */
5235         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5236       case NormalMove:
5237       case WhiteCapturesEnPassant:
5238       case BlackCapturesEnPassant:
5239         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5241         break;
5242     }
5243     SendToICS(user_move);
5244     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5245         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5246 }
5247
5248 void
5249 UploadGameEvent ()
5250 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5251     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5252     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5253     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5254       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5255       return;
5256     }
5257     if(gameMode != IcsExamining) { // is this ever not the case?
5258         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5259
5260         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5261           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5262         } else { // on FICS we must first go to general examine mode
5263           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5264         }
5265         if(gameInfo.variant != VariantNormal) {
5266             // try figure out wild number, as xboard names are not always valid on ICS
5267             for(i=1; i<=36; i++) {
5268               snprintf(buf, MSG_SIZ, "wild/%d", i);
5269                 if(StringToVariant(buf) == gameInfo.variant) break;
5270             }
5271             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5272             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5273             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5274         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5275         SendToICS(ics_prefix);
5276         SendToICS(buf);
5277         if(startedFromSetupPosition || backwardMostMove != 0) {
5278           fen = PositionToFEN(backwardMostMove, NULL, 1);
5279           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5280             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5281             SendToICS(buf);
5282           } else { // FICS: everything has to set by separate bsetup commands
5283             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5284             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5285             SendToICS(buf);
5286             if(!WhiteOnMove(backwardMostMove)) {
5287                 SendToICS("bsetup tomove black\n");
5288             }
5289             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5290             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5291             SendToICS(buf);
5292             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5293             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5294             SendToICS(buf);
5295             i = boards[backwardMostMove][EP_STATUS];
5296             if(i >= 0) { // set e.p.
5297               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5298                 SendToICS(buf);
5299             }
5300             bsetup++;
5301           }
5302         }
5303       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5304             SendToICS("bsetup done\n"); // switch to normal examining.
5305     }
5306     for(i = backwardMostMove; i<last; i++) {
5307         char buf[20];
5308         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5309         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5310             int len = strlen(moveList[i]);
5311             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5312             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5313         }
5314         SendToICS(buf);
5315     }
5316     SendToICS(ics_prefix);
5317     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5318 }
5319
5320 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5321
5322 void
5323 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5324 {
5325     if (rf == DROP_RANK) {
5326       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5327       sprintf(move, "%c@%c%c\n",
5328                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5329     } else {
5330         if (promoChar == 'x' || promoChar == NULLCHAR) {
5331           sprintf(move, "%c%c%c%c\n",
5332                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5333           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5334         } else {
5335             sprintf(move, "%c%c%c%c%c\n",
5336                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5337         }
5338     }
5339 }
5340
5341 void
5342 ProcessICSInitScript (FILE *f)
5343 {
5344     char buf[MSG_SIZ];
5345
5346     while (fgets(buf, MSG_SIZ, f)) {
5347         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5348     }
5349
5350     fclose(f);
5351 }
5352
5353
5354 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5355 int dragging;
5356 static ClickType lastClickType;
5357
5358 int
5359 Partner (ChessSquare *p)
5360 { // change piece into promotion partner if one shogi-promotes to the other
5361   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5362   ChessSquare partner;
5363   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5364   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5365   *p = partner;
5366   return 1;
5367 }
5368
5369 void
5370 Sweep (int step)
5371 {
5372     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5373     static int toggleFlag;
5374     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5375     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5376     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5377     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5378     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5379     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5380     do {
5381         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5382         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5383         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5384         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5385         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5386         if(!step) step = -1;
5387     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5388             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5389             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5390             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5391     if(toX >= 0) {
5392         int victim = boards[currentMove][toY][toX];
5393         boards[currentMove][toY][toX] = promoSweep;
5394         DrawPosition(FALSE, boards[currentMove]);
5395         boards[currentMove][toY][toX] = victim;
5396     } else
5397     ChangeDragPiece(promoSweep);
5398 }
5399
5400 int
5401 PromoScroll (int x, int y)
5402 {
5403   int step = 0;
5404
5405   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5406   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5407   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5408   if(!step) return FALSE;
5409   lastX = x; lastY = y;
5410   if((promoSweep < BlackPawn) == flipView) step = -step;
5411   if(step > 0) selectFlag = 1;
5412   if(!selectFlag) Sweep(step);
5413   return FALSE;
5414 }
5415
5416 void
5417 NextPiece (int step)
5418 {
5419     ChessSquare piece = boards[currentMove][toY][toX];
5420     do {
5421         pieceSweep -= step;
5422         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5423         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5424         if(!step) step = -1;
5425     } while(PieceToChar(pieceSweep) == '.');
5426     boards[currentMove][toY][toX] = pieceSweep;
5427     DrawPosition(FALSE, boards[currentMove]);
5428     boards[currentMove][toY][toX] = piece;
5429 }
5430 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5431 void
5432 AlphaRank (char *move, int n)
5433 {
5434 //    char *p = move, c; int x, y;
5435
5436     if (appData.debugMode) {
5437         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5438     }
5439
5440     if(move[1]=='*' &&
5441        move[2]>='0' && move[2]<='9' &&
5442        move[3]>='a' && move[3]<='x'    ) {
5443         move[1] = '@';
5444         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5445         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5446     } else
5447     if(move[0]>='0' && move[0]<='9' &&
5448        move[1]>='a' && move[1]<='x' &&
5449        move[2]>='0' && move[2]<='9' &&
5450        move[3]>='a' && move[3]<='x'    ) {
5451         /* input move, Shogi -> normal */
5452         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5453         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5454         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5455         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5456     } else
5457     if(move[1]=='@' &&
5458        move[3]>='0' && move[3]<='9' &&
5459        move[2]>='a' && move[2]<='x'    ) {
5460         move[1] = '*';
5461         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5462         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5463     } else
5464     if(
5465        move[0]>='a' && move[0]<='x' &&
5466        move[3]>='0' && move[3]<='9' &&
5467        move[2]>='a' && move[2]<='x'    ) {
5468          /* output move, normal -> Shogi */
5469         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5470         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5471         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5472         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5473         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5474     }
5475     if (appData.debugMode) {
5476         fprintf(debugFP, "   out = '%s'\n", move);
5477     }
5478 }
5479
5480 char yy_textstr[8000];
5481
5482 /* Parser for moves from gnuchess, ICS, or user typein box */
5483 Boolean
5484 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5485 {
5486     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5487
5488     switch (*moveType) {
5489       case WhitePromotion:
5490       case BlackPromotion:
5491       case WhiteNonPromotion:
5492       case BlackNonPromotion:
5493       case NormalMove:
5494       case FirstLeg:
5495       case WhiteCapturesEnPassant:
5496       case BlackCapturesEnPassant:
5497       case WhiteKingSideCastle:
5498       case WhiteQueenSideCastle:
5499       case BlackKingSideCastle:
5500       case BlackQueenSideCastle:
5501       case WhiteKingSideCastleWild:
5502       case WhiteQueenSideCastleWild:
5503       case BlackKingSideCastleWild:
5504       case BlackQueenSideCastleWild:
5505       /* Code added by Tord: */
5506       case WhiteHSideCastleFR:
5507       case WhiteASideCastleFR:
5508       case BlackHSideCastleFR:
5509       case BlackASideCastleFR:
5510       /* End of code added by Tord */
5511       case IllegalMove:         /* bug or odd chess variant */
5512         *fromX = currentMoveString[0] - AAA;
5513         *fromY = currentMoveString[1] - ONE;
5514         *toX = currentMoveString[2] - AAA;
5515         *toY = currentMoveString[3] - ONE;
5516         *promoChar = currentMoveString[4];
5517         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5518             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5519     if (appData.debugMode) {
5520         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5521     }
5522             *fromX = *fromY = *toX = *toY = 0;
5523             return FALSE;
5524         }
5525         if (appData.testLegality) {
5526           return (*moveType != IllegalMove);
5527         } else {
5528           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5529                          // [HGM] lion: if this is a double move we are less critical
5530                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5531         }
5532
5533       case WhiteDrop:
5534       case BlackDrop:
5535         *fromX = *moveType == WhiteDrop ?
5536           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5537           (int) CharToPiece(ToLower(currentMoveString[0]));
5538         *fromY = DROP_RANK;
5539         *toX = currentMoveString[2] - AAA;
5540         *toY = currentMoveString[3] - ONE;
5541         *promoChar = NULLCHAR;
5542         return TRUE;
5543
5544       case AmbiguousMove:
5545       case ImpossibleMove:
5546       case EndOfFile:
5547       case ElapsedTime:
5548       case Comment:
5549       case PGNTag:
5550       case NAG:
5551       case WhiteWins:
5552       case BlackWins:
5553       case GameIsDrawn:
5554       default:
5555     if (appData.debugMode) {
5556         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5557     }
5558         /* bug? */
5559         *fromX = *fromY = *toX = *toY = 0;
5560         *promoChar = NULLCHAR;
5561         return FALSE;
5562     }
5563 }
5564
5565 Boolean pushed = FALSE;
5566 char *lastParseAttempt;
5567
5568 void
5569 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5570 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5571   int fromX, fromY, toX, toY; char promoChar;
5572   ChessMove moveType;
5573   Boolean valid;
5574   int nr = 0;
5575
5576   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5577   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5578     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5579     pushed = TRUE;
5580   }
5581   endPV = forwardMostMove;
5582   do {
5583     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5584     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5585     lastParseAttempt = pv;
5586     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5587     if(!valid && nr == 0 &&
5588        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5589         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5590         // Hande case where played move is different from leading PV move
5591         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5592         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5593         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5594         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5595           endPV += 2; // if position different, keep this
5596           moveList[endPV-1][0] = fromX + AAA;
5597           moveList[endPV-1][1] = fromY + ONE;
5598           moveList[endPV-1][2] = toX + AAA;
5599           moveList[endPV-1][3] = toY + ONE;
5600           parseList[endPV-1][0] = NULLCHAR;
5601           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5602         }
5603       }
5604     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5605     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5606     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5607     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5608         valid++; // allow comments in PV
5609         continue;
5610     }
5611     nr++;
5612     if(endPV+1 > framePtr) break; // no space, truncate
5613     if(!valid) break;
5614     endPV++;
5615     CopyBoard(boards[endPV], boards[endPV-1]);
5616     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5617     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5618     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5619     CoordsToAlgebraic(boards[endPV - 1],
5620                              PosFlags(endPV - 1),
5621                              fromY, fromX, toY, toX, promoChar,
5622                              parseList[endPV - 1]);
5623   } while(valid);
5624   if(atEnd == 2) return; // used hidden, for PV conversion
5625   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5626   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5627   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5628                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5629   DrawPosition(TRUE, boards[currentMove]);
5630 }
5631
5632 int
5633 MultiPV (ChessProgramState *cps)
5634 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5635         int i;
5636         for(i=0; i<cps->nrOptions; i++)
5637             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5638                 return i;
5639         return -1;
5640 }
5641
5642 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5643
5644 Boolean
5645 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5646 {
5647         int startPV, multi, lineStart, origIndex = index;
5648         char *p, buf2[MSG_SIZ];
5649         ChessProgramState *cps = (pane ? &second : &first);
5650
5651         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5652         lastX = x; lastY = y;
5653         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5654         lineStart = startPV = index;
5655         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5656         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5657         index = startPV;
5658         do{ while(buf[index] && buf[index] != '\n') index++;
5659         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5660         buf[index] = 0;
5661         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5662                 int n = cps->option[multi].value;
5663                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5664                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5665                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5666                 cps->option[multi].value = n;
5667                 *start = *end = 0;
5668                 return FALSE;
5669         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5670                 ExcludeClick(origIndex - lineStart);
5671                 return FALSE;
5672         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5673                 Collapse(origIndex - lineStart);
5674                 return FALSE;
5675         }
5676         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5677         *start = startPV; *end = index-1;
5678         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5679         return TRUE;
5680 }
5681
5682 char *
5683 PvToSAN (char *pv)
5684 {
5685         static char buf[10*MSG_SIZ];
5686         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5687         *buf = NULLCHAR;
5688         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5689         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5690         for(i = forwardMostMove; i<endPV; i++){
5691             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5692             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5693             k += strlen(buf+k);
5694         }
5695         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5696         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5697         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5698         endPV = savedEnd;
5699         return buf;
5700 }
5701
5702 Boolean
5703 LoadPV (int x, int y)
5704 { // called on right mouse click to load PV
5705   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5706   lastX = x; lastY = y;
5707   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5708   extendGame = FALSE;
5709   return TRUE;
5710 }
5711
5712 void
5713 UnLoadPV ()
5714 {
5715   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5716   if(endPV < 0) return;
5717   if(appData.autoCopyPV) CopyFENToClipboard();
5718   endPV = -1;
5719   if(extendGame && currentMove > forwardMostMove) {
5720         Boolean saveAnimate = appData.animate;
5721         if(pushed) {
5722             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5723                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5724             } else storedGames--; // abandon shelved tail of original game
5725         }
5726         pushed = FALSE;
5727         forwardMostMove = currentMove;
5728         currentMove = oldFMM;
5729         appData.animate = FALSE;
5730         ToNrEvent(forwardMostMove);
5731         appData.animate = saveAnimate;
5732   }
5733   currentMove = forwardMostMove;
5734   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5735   ClearPremoveHighlights();
5736   DrawPosition(TRUE, boards[currentMove]);
5737 }
5738
5739 void
5740 MovePV (int x, int y, int h)
5741 { // step through PV based on mouse coordinates (called on mouse move)
5742   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5743
5744   // we must somehow check if right button is still down (might be released off board!)
5745   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5746   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5747   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5748   if(!step) return;
5749   lastX = x; lastY = y;
5750
5751   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5752   if(endPV < 0) return;
5753   if(y < margin) step = 1; else
5754   if(y > h - margin) step = -1;
5755   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5756   currentMove += step;
5757   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5758   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5759                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5760   DrawPosition(FALSE, boards[currentMove]);
5761 }
5762
5763
5764 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5765 // All positions will have equal probability, but the current method will not provide a unique
5766 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5767 #define DARK 1
5768 #define LITE 2
5769 #define ANY 3
5770
5771 int squaresLeft[4];
5772 int piecesLeft[(int)BlackPawn];
5773 int seed, nrOfShuffles;
5774
5775 void
5776 GetPositionNumber ()
5777 {       // sets global variable seed
5778         int i;
5779
5780         seed = appData.defaultFrcPosition;
5781         if(seed < 0) { // randomize based on time for negative FRC position numbers
5782                 for(i=0; i<50; i++) seed += random();
5783                 seed = random() ^ random() >> 8 ^ random() << 8;
5784                 if(seed<0) seed = -seed;
5785         }
5786 }
5787
5788 int
5789 put (Board board, int pieceType, int rank, int n, int shade)
5790 // put the piece on the (n-1)-th empty squares of the given shade
5791 {
5792         int i;
5793
5794         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5795                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5796                         board[rank][i] = (ChessSquare) pieceType;
5797                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5798                         squaresLeft[ANY]--;
5799                         piecesLeft[pieceType]--;
5800                         return i;
5801                 }
5802         }
5803         return -1;
5804 }
5805
5806
5807 void
5808 AddOnePiece (Board board, int pieceType, int rank, int shade)
5809 // calculate where the next piece goes, (any empty square), and put it there
5810 {
5811         int i;
5812
5813         i = seed % squaresLeft[shade];
5814         nrOfShuffles *= squaresLeft[shade];
5815         seed /= squaresLeft[shade];
5816         put(board, pieceType, rank, i, shade);
5817 }
5818
5819 void
5820 AddTwoPieces (Board board, int pieceType, int rank)
5821 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5822 {
5823         int i, n=squaresLeft[ANY], j=n-1, k;
5824
5825         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5826         i = seed % k;  // pick one
5827         nrOfShuffles *= k;
5828         seed /= k;
5829         while(i >= j) i -= j--;
5830         j = n - 1 - j; i += j;
5831         put(board, pieceType, rank, j, ANY);
5832         put(board, pieceType, rank, i, ANY);
5833 }
5834
5835 void
5836 SetUpShuffle (Board board, int number)
5837 {
5838         int i, p, first=1;
5839
5840         GetPositionNumber(); nrOfShuffles = 1;
5841
5842         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5843         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5844         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5845
5846         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5847
5848         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5849             p = (int) board[0][i];
5850             if(p < (int) BlackPawn) piecesLeft[p] ++;
5851             board[0][i] = EmptySquare;
5852         }
5853
5854         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5855             // shuffles restricted to allow normal castling put KRR first
5856             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5857                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5858             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5859                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5860             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5861                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5862             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5863                 put(board, WhiteRook, 0, 0, ANY);
5864             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5865         }
5866
5867         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5868             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5869             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5870                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5871                 while(piecesLeft[p] >= 2) {
5872                     AddOnePiece(board, p, 0, LITE);
5873                     AddOnePiece(board, p, 0, DARK);
5874                 }
5875                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5876             }
5877
5878         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5879             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5880             // but we leave King and Rooks for last, to possibly obey FRC restriction
5881             if(p == (int)WhiteRook) continue;
5882             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5883             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5884         }
5885
5886         // now everything is placed, except perhaps King (Unicorn) and Rooks
5887
5888         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5889             // Last King gets castling rights
5890             while(piecesLeft[(int)WhiteUnicorn]) {
5891                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5892                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5893             }
5894
5895             while(piecesLeft[(int)WhiteKing]) {
5896                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5897                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5898             }
5899
5900
5901         } else {
5902             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5903             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5904         }
5905
5906         // Only Rooks can be left; simply place them all
5907         while(piecesLeft[(int)WhiteRook]) {
5908                 i = put(board, WhiteRook, 0, 0, ANY);
5909                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5910                         if(first) {
5911                                 first=0;
5912                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5913                         }
5914                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5915                 }
5916         }
5917         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5918             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5919         }
5920
5921         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5922 }
5923
5924 int
5925 SetCharTable (char *table, const char * map)
5926 /* [HGM] moved here from winboard.c because of its general usefulness */
5927 /*       Basically a safe strcpy that uses the last character as King */
5928 {
5929     int result = FALSE; int NrPieces;
5930
5931     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5932                     && NrPieces >= 12 && !(NrPieces&1)) {
5933         int i; /* [HGM] Accept even length from 12 to 34 */
5934
5935         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5936         for( i=0; i<NrPieces/2-1; i++ ) {
5937             table[i] = map[i];
5938             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5939         }
5940         table[(int) WhiteKing]  = map[NrPieces/2-1];
5941         table[(int) BlackKing]  = map[NrPieces-1];
5942
5943         result = TRUE;
5944     }
5945
5946     return result;
5947 }
5948
5949 void
5950 Prelude (Board board)
5951 {       // [HGM] superchess: random selection of exo-pieces
5952         int i, j, k; ChessSquare p;
5953         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5954
5955         GetPositionNumber(); // use FRC position number
5956
5957         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5958             SetCharTable(pieceToChar, appData.pieceToCharTable);
5959             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5960                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5961         }
5962
5963         j = seed%4;                 seed /= 4;
5964         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5965         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5966         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5967         j = seed%3 + (seed%3 >= j); seed /= 3;
5968         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5969         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5970         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5971         j = seed%3;                 seed /= 3;
5972         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5973         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5974         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5975         j = seed%2 + (seed%2 >= j); seed /= 2;
5976         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5977         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5978         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5979         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5980         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5981         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5982         put(board, exoPieces[0],    0, 0, ANY);
5983         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5984 }
5985
5986 void
5987 InitPosition (int redraw)
5988 {
5989     ChessSquare (* pieces)[BOARD_FILES];
5990     int i, j, pawnRow=1, pieceRows=1, overrule,
5991     oldx = gameInfo.boardWidth,
5992     oldy = gameInfo.boardHeight,
5993     oldh = gameInfo.holdingsWidth;
5994     static int oldv;
5995
5996     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5997
5998     /* [AS] Initialize pv info list [HGM] and game status */
5999     {
6000         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6001             pvInfoList[i].depth = 0;
6002             boards[i][EP_STATUS] = EP_NONE;
6003             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6004         }
6005
6006         initialRulePlies = 0; /* 50-move counter start */
6007
6008         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6009         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6010     }
6011
6012
6013     /* [HGM] logic here is completely changed. In stead of full positions */
6014     /* the initialized data only consist of the two backranks. The switch */
6015     /* selects which one we will use, which is than copied to the Board   */
6016     /* initialPosition, which for the rest is initialized by Pawns and    */
6017     /* empty squares. This initial position is then copied to boards[0],  */
6018     /* possibly after shuffling, so that it remains available.            */
6019
6020     gameInfo.holdingsWidth = 0; /* default board sizes */
6021     gameInfo.boardWidth    = 8;
6022     gameInfo.boardHeight   = 8;
6023     gameInfo.holdingsSize  = 0;
6024     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6025     for(i=0; i<BOARD_FILES-2; i++)
6026       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6027     initialPosition[EP_STATUS] = EP_NONE;
6028     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6029     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6030          SetCharTable(pieceNickName, appData.pieceNickNames);
6031     else SetCharTable(pieceNickName, "............");
6032     pieces = FIDEArray;
6033
6034     switch (gameInfo.variant) {
6035     case VariantFischeRandom:
6036       shuffleOpenings = TRUE;
6037       appData.fischerCastling = TRUE;
6038     default:
6039       break;
6040     case VariantShatranj:
6041       pieces = ShatranjArray;
6042       nrCastlingRights = 0;
6043       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6044       break;
6045     case VariantMakruk:
6046       pieces = makrukArray;
6047       nrCastlingRights = 0;
6048       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6049       break;
6050     case VariantASEAN:
6051       pieces = aseanArray;
6052       nrCastlingRights = 0;
6053       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6054       break;
6055     case VariantTwoKings:
6056       pieces = twoKingsArray;
6057       break;
6058     case VariantGrand:
6059       pieces = GrandArray;
6060       nrCastlingRights = 0;
6061       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6062       gameInfo.boardWidth = 10;
6063       gameInfo.boardHeight = 10;
6064       gameInfo.holdingsSize = 7;
6065       break;
6066     case VariantCapaRandom:
6067       shuffleOpenings = TRUE;
6068       appData.fischerCastling = TRUE;
6069     case VariantCapablanca:
6070       pieces = CapablancaArray;
6071       gameInfo.boardWidth = 10;
6072       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6073       break;
6074     case VariantGothic:
6075       pieces = GothicArray;
6076       gameInfo.boardWidth = 10;
6077       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6078       break;
6079     case VariantSChess:
6080       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6081       gameInfo.holdingsSize = 7;
6082       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6083       break;
6084     case VariantJanus:
6085       pieces = JanusArray;
6086       gameInfo.boardWidth = 10;
6087       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6088       nrCastlingRights = 6;
6089         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6090         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6091         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6092         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6093         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6094         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6095       break;
6096     case VariantFalcon:
6097       pieces = FalconArray;
6098       gameInfo.boardWidth = 10;
6099       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6100       break;
6101     case VariantXiangqi:
6102       pieces = XiangqiArray;
6103       gameInfo.boardWidth  = 9;
6104       gameInfo.boardHeight = 10;
6105       nrCastlingRights = 0;
6106       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6107       break;
6108     case VariantShogi:
6109       pieces = ShogiArray;
6110       gameInfo.boardWidth  = 9;
6111       gameInfo.boardHeight = 9;
6112       gameInfo.holdingsSize = 7;
6113       nrCastlingRights = 0;
6114       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6115       break;
6116     case VariantChu:
6117       pieces = ChuArray; pieceRows = 3;
6118       gameInfo.boardWidth  = 12;
6119       gameInfo.boardHeight = 12;
6120       nrCastlingRights = 0;
6121       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6122                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6123       break;
6124     case VariantCourier:
6125       pieces = CourierArray;
6126       gameInfo.boardWidth  = 12;
6127       nrCastlingRights = 0;
6128       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6129       break;
6130     case VariantKnightmate:
6131       pieces = KnightmateArray;
6132       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6133       break;
6134     case VariantSpartan:
6135       pieces = SpartanArray;
6136       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6137       break;
6138     case VariantLion:
6139       pieces = lionArray;
6140       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6141       break;
6142     case VariantChuChess:
6143       pieces = ChuChessArray;
6144       gameInfo.boardWidth = 10;
6145       gameInfo.boardHeight = 10;
6146       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6147       break;
6148     case VariantFairy:
6149       pieces = fairyArray;
6150       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6151       break;
6152     case VariantGreat:
6153       pieces = GreatArray;
6154       gameInfo.boardWidth = 10;
6155       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6156       gameInfo.holdingsSize = 8;
6157       break;
6158     case VariantSuper:
6159       pieces = FIDEArray;
6160       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6161       gameInfo.holdingsSize = 8;
6162       startedFromSetupPosition = TRUE;
6163       break;
6164     case VariantCrazyhouse:
6165     case VariantBughouse:
6166       pieces = FIDEArray;
6167       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6168       gameInfo.holdingsSize = 5;
6169       break;
6170     case VariantWildCastle:
6171       pieces = FIDEArray;
6172       /* !!?shuffle with kings guaranteed to be on d or e file */
6173       shuffleOpenings = 1;
6174       break;
6175     case VariantNoCastle:
6176       pieces = FIDEArray;
6177       nrCastlingRights = 0;
6178       /* !!?unconstrained back-rank shuffle */
6179       shuffleOpenings = 1;
6180       break;
6181     }
6182
6183     overrule = 0;
6184     if(appData.NrFiles >= 0) {
6185         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6186         gameInfo.boardWidth = appData.NrFiles;
6187     }
6188     if(appData.NrRanks >= 0) {
6189         gameInfo.boardHeight = appData.NrRanks;
6190     }
6191     if(appData.holdingsSize >= 0) {
6192         i = appData.holdingsSize;
6193         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6194         gameInfo.holdingsSize = i;
6195     }
6196     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6197     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6198         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6199
6200     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6201     if(pawnRow < 1) pawnRow = 1;
6202     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6203        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6204     if(gameInfo.variant == VariantChu) pawnRow = 3;
6205
6206     /* User pieceToChar list overrules defaults */
6207     if(appData.pieceToCharTable != NULL)
6208         SetCharTable(pieceToChar, appData.pieceToCharTable);
6209
6210     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6211
6212         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6213             s = (ChessSquare) 0; /* account holding counts in guard band */
6214         for( i=0; i<BOARD_HEIGHT; i++ )
6215             initialPosition[i][j] = s;
6216
6217         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6218         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6219         initialPosition[pawnRow][j] = WhitePawn;
6220         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6221         if(gameInfo.variant == VariantXiangqi) {
6222             if(j&1) {
6223                 initialPosition[pawnRow][j] =
6224                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6225                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6226                    initialPosition[2][j] = WhiteCannon;
6227                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6228                 }
6229             }
6230         }
6231         if(gameInfo.variant == VariantChu) {
6232              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6233                initialPosition[pawnRow+1][j] = WhiteCobra,
6234                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6235              for(i=1; i<pieceRows; i++) {
6236                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6237                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6238              }
6239         }
6240         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6241             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6242                initialPosition[0][j] = WhiteRook;
6243                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6244             }
6245         }
6246         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6247     }
6248     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6249     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6250
6251             j=BOARD_LEFT+1;
6252             initialPosition[1][j] = WhiteBishop;
6253             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6254             j=BOARD_RGHT-2;
6255             initialPosition[1][j] = WhiteRook;
6256             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6257     }
6258
6259     if( nrCastlingRights == -1) {
6260         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6261         /*       This sets default castling rights from none to normal corners   */
6262         /* Variants with other castling rights must set them themselves above    */
6263         nrCastlingRights = 6;
6264
6265         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6266         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6267         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6268         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6269         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6270         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6271      }
6272
6273      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6274      if(gameInfo.variant == VariantGreat) { // promotion commoners
6275         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6276         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6277         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6278         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6279      }
6280      if( gameInfo.variant == VariantSChess ) {
6281       initialPosition[1][0] = BlackMarshall;
6282       initialPosition[2][0] = BlackAngel;
6283       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6284       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6285       initialPosition[1][1] = initialPosition[2][1] =
6286       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6287      }
6288   if (appData.debugMode) {
6289     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6290   }
6291     if(shuffleOpenings) {
6292         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6293         startedFromSetupPosition = TRUE;
6294     }
6295     if(startedFromPositionFile) {
6296       /* [HGM] loadPos: use PositionFile for every new game */
6297       CopyBoard(initialPosition, filePosition);
6298       for(i=0; i<nrCastlingRights; i++)
6299           initialRights[i] = filePosition[CASTLING][i];
6300       startedFromSetupPosition = TRUE;
6301     }
6302
6303     CopyBoard(boards[0], initialPosition);
6304
6305     if(oldx != gameInfo.boardWidth ||
6306        oldy != gameInfo.boardHeight ||
6307        oldv != gameInfo.variant ||
6308        oldh != gameInfo.holdingsWidth
6309                                          )
6310             InitDrawingSizes(-2 ,0);
6311
6312     oldv = gameInfo.variant;
6313     if (redraw)
6314       DrawPosition(TRUE, boards[currentMove]);
6315 }
6316
6317 void
6318 SendBoard (ChessProgramState *cps, int moveNum)
6319 {
6320     char message[MSG_SIZ];
6321
6322     if (cps->useSetboard) {
6323       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6324       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6325       SendToProgram(message, cps);
6326       free(fen);
6327
6328     } else {
6329       ChessSquare *bp;
6330       int i, j, left=0, right=BOARD_WIDTH;
6331       /* Kludge to set black to move, avoiding the troublesome and now
6332        * deprecated "black" command.
6333        */
6334       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6335         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6336
6337       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6338
6339       SendToProgram("edit\n", cps);
6340       SendToProgram("#\n", cps);
6341       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6342         bp = &boards[moveNum][i][left];
6343         for (j = left; j < right; j++, bp++) {
6344           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6345           if ((int) *bp < (int) BlackPawn) {
6346             if(j == BOARD_RGHT+1)
6347                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6348             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6349             if(message[0] == '+' || message[0] == '~') {
6350               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6351                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6352                         AAA + j, ONE + i);
6353             }
6354             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6355                 message[1] = BOARD_RGHT   - 1 - j + '1';
6356                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6357             }
6358             SendToProgram(message, cps);
6359           }
6360         }
6361       }
6362
6363       SendToProgram("c\n", cps);
6364       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6365         bp = &boards[moveNum][i][left];
6366         for (j = left; j < right; j++, bp++) {
6367           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6368           if (((int) *bp != (int) EmptySquare)
6369               && ((int) *bp >= (int) BlackPawn)) {
6370             if(j == BOARD_LEFT-2)
6371                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6372             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6373                     AAA + j, ONE + i);
6374             if(message[0] == '+' || message[0] == '~') {
6375               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6376                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6377                         AAA + j, ONE + i);
6378             }
6379             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6380                 message[1] = BOARD_RGHT   - 1 - j + '1';
6381                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6382             }
6383             SendToProgram(message, cps);
6384           }
6385         }
6386       }
6387
6388       SendToProgram(".\n", cps);
6389     }
6390     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6391 }
6392
6393 char exclusionHeader[MSG_SIZ];
6394 int exCnt, excludePtr;
6395 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6396 static Exclusion excluTab[200];
6397 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6398
6399 static void
6400 WriteMap (int s)
6401 {
6402     int j;
6403     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6404     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6405 }
6406
6407 static void
6408 ClearMap ()
6409 {
6410     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6411     excludePtr = 24; exCnt = 0;
6412     WriteMap(0);
6413 }
6414
6415 static void
6416 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6417 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6418     char buf[2*MOVE_LEN], *p;
6419     Exclusion *e = excluTab;
6420     int i;
6421     for(i=0; i<exCnt; i++)
6422         if(e[i].ff == fromX && e[i].fr == fromY &&
6423            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6424     if(i == exCnt) { // was not in exclude list; add it
6425         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6426         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6427             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6428             return; // abort
6429         }
6430         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6431         excludePtr++; e[i].mark = excludePtr++;
6432         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6433         exCnt++;
6434     }
6435     exclusionHeader[e[i].mark] = state;
6436 }
6437
6438 static int
6439 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6440 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6441     char buf[MSG_SIZ];
6442     int j, k;
6443     ChessMove moveType;
6444     if((signed char)promoChar == -1) { // kludge to indicate best move
6445         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6446             return 1; // if unparsable, abort
6447     }
6448     // update exclusion map (resolving toggle by consulting existing state)
6449     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6450     j = k%8; k >>= 3;
6451     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6452     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6453          excludeMap[k] |=   1<<j;
6454     else excludeMap[k] &= ~(1<<j);
6455     // update header
6456     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6457     // inform engine
6458     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6459     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6460     SendToBoth(buf);
6461     return (state == '+');
6462 }
6463
6464 static void
6465 ExcludeClick (int index)
6466 {
6467     int i, j;
6468     Exclusion *e = excluTab;
6469     if(index < 25) { // none, best or tail clicked
6470         if(index < 13) { // none: include all
6471             WriteMap(0); // clear map
6472             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6473             SendToBoth("include all\n"); // and inform engine
6474         } else if(index > 18) { // tail
6475             if(exclusionHeader[19] == '-') { // tail was excluded
6476                 SendToBoth("include all\n");
6477                 WriteMap(0); // clear map completely
6478                 // now re-exclude selected moves
6479                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6480                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6481             } else { // tail was included or in mixed state
6482                 SendToBoth("exclude all\n");
6483                 WriteMap(0xFF); // fill map completely
6484                 // now re-include selected moves
6485                 j = 0; // count them
6486                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6487                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6488                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6489             }
6490         } else { // best
6491             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6492         }
6493     } else {
6494         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6495             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6496             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6497             break;
6498         }
6499     }
6500 }
6501
6502 ChessSquare
6503 DefaultPromoChoice (int white)
6504 {
6505     ChessSquare result;
6506     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6507        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6508         result = WhiteFerz; // no choice
6509     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6510         result= WhiteKing; // in Suicide Q is the last thing we want
6511     else if(gameInfo.variant == VariantSpartan)
6512         result = white ? WhiteQueen : WhiteAngel;
6513     else result = WhiteQueen;
6514     if(!white) result = WHITE_TO_BLACK result;
6515     return result;
6516 }
6517
6518 static int autoQueen; // [HGM] oneclick
6519
6520 int
6521 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6522 {
6523     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6524     /* [HGM] add Shogi promotions */
6525     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6526     ChessSquare piece, partner;
6527     ChessMove moveType;
6528     Boolean premove;
6529
6530     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6531     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6532
6533     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6534       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6535         return FALSE;
6536
6537     piece = boards[currentMove][fromY][fromX];
6538     if(gameInfo.variant == VariantChu) {
6539         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6540         promotionZoneSize = BOARD_HEIGHT/3;
6541         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6542     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6543         promotionZoneSize = BOARD_HEIGHT/3;
6544         highestPromotingPiece = (int)WhiteAlfil;
6545     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6546         promotionZoneSize = 3;
6547     }
6548
6549     // Treat Lance as Pawn when it is not representing Amazon or Lance
6550     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6551         if(piece == WhiteLance) piece = WhitePawn; else
6552         if(piece == BlackLance) piece = BlackPawn;
6553     }
6554
6555     // next weed out all moves that do not touch the promotion zone at all
6556     if((int)piece >= BlackPawn) {
6557         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6558              return FALSE;
6559         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6560         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6561     } else {
6562         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6563            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6564         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6565              return FALSE;
6566     }
6567
6568     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6569
6570     // weed out mandatory Shogi promotions
6571     if(gameInfo.variant == VariantShogi) {
6572         if(piece >= BlackPawn) {
6573             if(toY == 0 && piece == BlackPawn ||
6574                toY == 0 && piece == BlackQueen ||
6575                toY <= 1 && piece == BlackKnight) {
6576                 *promoChoice = '+';
6577                 return FALSE;
6578             }
6579         } else {
6580             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6581                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6582                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6583                 *promoChoice = '+';
6584                 return FALSE;
6585             }
6586         }
6587     }
6588
6589     // weed out obviously illegal Pawn moves
6590     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6591         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6592         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6593         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6594         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6595         // note we are not allowed to test for valid (non-)capture, due to premove
6596     }
6597
6598     // we either have a choice what to promote to, or (in Shogi) whether to promote
6599     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6600        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6601         ChessSquare p=BlackFerz;  // no choice
6602         while(p < EmptySquare) {  //but make sure we use piece that exists
6603             *promoChoice = PieceToChar(p++);
6604             if(*promoChoice != '.') break;
6605         }
6606         return FALSE;
6607     }
6608     // no sense asking what we must promote to if it is going to explode...
6609     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6610         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6611         return FALSE;
6612     }
6613     // give caller the default choice even if we will not make it
6614     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6615     partner = piece; // pieces can promote if the pieceToCharTable says so
6616     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6617     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6618     if(        sweepSelect && gameInfo.variant != VariantGreat
6619                            && gameInfo.variant != VariantGrand
6620                            && gameInfo.variant != VariantSuper) return FALSE;
6621     if(autoQueen) return FALSE; // predetermined
6622
6623     // suppress promotion popup on illegal moves that are not premoves
6624     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6625               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6626     if(appData.testLegality && !premove) {
6627         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6628                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6629         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6630         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6631             return FALSE;
6632     }
6633
6634     return TRUE;
6635 }
6636
6637 int
6638 InPalace (int row, int column)
6639 {   /* [HGM] for Xiangqi */
6640     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6641          column < (BOARD_WIDTH + 4)/2 &&
6642          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6643     return FALSE;
6644 }
6645
6646 int
6647 PieceForSquare (int x, int y)
6648 {
6649   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6650      return -1;
6651   else
6652      return boards[currentMove][y][x];
6653 }
6654
6655 int
6656 OKToStartUserMove (int x, int y)
6657 {
6658     ChessSquare from_piece;
6659     int white_piece;
6660
6661     if (matchMode) return FALSE;
6662     if (gameMode == EditPosition) return TRUE;
6663
6664     if (x >= 0 && y >= 0)
6665       from_piece = boards[currentMove][y][x];
6666     else
6667       from_piece = EmptySquare;
6668
6669     if (from_piece == EmptySquare) return FALSE;
6670
6671     white_piece = (int)from_piece >= (int)WhitePawn &&
6672       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6673
6674     switch (gameMode) {
6675       case AnalyzeFile:
6676       case TwoMachinesPlay:
6677       case EndOfGame:
6678         return FALSE;
6679
6680       case IcsObserving:
6681       case IcsIdle:
6682         return FALSE;
6683
6684       case MachinePlaysWhite:
6685       case IcsPlayingBlack:
6686         if (appData.zippyPlay) return FALSE;
6687         if (white_piece) {
6688             DisplayMoveError(_("You are playing Black"));
6689             return FALSE;
6690         }
6691         break;
6692
6693       case MachinePlaysBlack:
6694       case IcsPlayingWhite:
6695         if (appData.zippyPlay) return FALSE;
6696         if (!white_piece) {
6697             DisplayMoveError(_("You are playing White"));
6698             return FALSE;
6699         }
6700         break;
6701
6702       case PlayFromGameFile:
6703             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6704       case EditGame:
6705         if (!white_piece && WhiteOnMove(currentMove)) {
6706             DisplayMoveError(_("It is White's turn"));
6707             return FALSE;
6708         }
6709         if (white_piece && !WhiteOnMove(currentMove)) {
6710             DisplayMoveError(_("It is Black's turn"));
6711             return FALSE;
6712         }
6713         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6714             /* Editing correspondence game history */
6715             /* Could disallow this or prompt for confirmation */
6716             cmailOldMove = -1;
6717         }
6718         break;
6719
6720       case BeginningOfGame:
6721         if (appData.icsActive) return FALSE;
6722         if (!appData.noChessProgram) {
6723             if (!white_piece) {
6724                 DisplayMoveError(_("You are playing White"));
6725                 return FALSE;
6726             }
6727         }
6728         break;
6729
6730       case Training:
6731         if (!white_piece && WhiteOnMove(currentMove)) {
6732             DisplayMoveError(_("It is White's turn"));
6733             return FALSE;
6734         }
6735         if (white_piece && !WhiteOnMove(currentMove)) {
6736             DisplayMoveError(_("It is Black's turn"));
6737             return FALSE;
6738         }
6739         break;
6740
6741       default:
6742       case IcsExamining:
6743         break;
6744     }
6745     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6746         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6747         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6748         && gameMode != AnalyzeFile && gameMode != Training) {
6749         DisplayMoveError(_("Displayed position is not current"));
6750         return FALSE;
6751     }
6752     return TRUE;
6753 }
6754
6755 Boolean
6756 OnlyMove (int *x, int *y, Boolean captures)
6757 {
6758     DisambiguateClosure cl;
6759     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6760     switch(gameMode) {
6761       case MachinePlaysBlack:
6762       case IcsPlayingWhite:
6763       case BeginningOfGame:
6764         if(!WhiteOnMove(currentMove)) return FALSE;
6765         break;
6766       case MachinePlaysWhite:
6767       case IcsPlayingBlack:
6768         if(WhiteOnMove(currentMove)) return FALSE;
6769         break;
6770       case EditGame:
6771         break;
6772       default:
6773         return FALSE;
6774     }
6775     cl.pieceIn = EmptySquare;
6776     cl.rfIn = *y;
6777     cl.ffIn = *x;
6778     cl.rtIn = -1;
6779     cl.ftIn = -1;
6780     cl.promoCharIn = NULLCHAR;
6781     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6782     if( cl.kind == NormalMove ||
6783         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6784         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6785         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6786       fromX = cl.ff;
6787       fromY = cl.rf;
6788       *x = cl.ft;
6789       *y = cl.rt;
6790       return TRUE;
6791     }
6792     if(cl.kind != ImpossibleMove) return FALSE;
6793     cl.pieceIn = EmptySquare;
6794     cl.rfIn = -1;
6795     cl.ffIn = -1;
6796     cl.rtIn = *y;
6797     cl.ftIn = *x;
6798     cl.promoCharIn = NULLCHAR;
6799     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6800     if( cl.kind == NormalMove ||
6801         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6802         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6803         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6804       fromX = cl.ff;
6805       fromY = cl.rf;
6806       *x = cl.ft;
6807       *y = cl.rt;
6808       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6809       return TRUE;
6810     }
6811     return FALSE;
6812 }
6813
6814 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6815 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6816 int lastLoadGameUseList = FALSE;
6817 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6818 ChessMove lastLoadGameStart = EndOfFile;
6819 int doubleClick;
6820 Boolean addToBookFlag;
6821
6822 void
6823 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6824 {
6825     ChessMove moveType;
6826     ChessSquare pup;
6827     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6828
6829     /* Check if the user is playing in turn.  This is complicated because we
6830        let the user "pick up" a piece before it is his turn.  So the piece he
6831        tried to pick up may have been captured by the time he puts it down!
6832        Therefore we use the color the user is supposed to be playing in this
6833        test, not the color of the piece that is currently on the starting
6834        square---except in EditGame mode, where the user is playing both
6835        sides; fortunately there the capture race can't happen.  (It can
6836        now happen in IcsExamining mode, but that's just too bad.  The user
6837        will get a somewhat confusing message in that case.)
6838        */
6839
6840     switch (gameMode) {
6841       case AnalyzeFile:
6842       case TwoMachinesPlay:
6843       case EndOfGame:
6844       case IcsObserving:
6845       case IcsIdle:
6846         /* We switched into a game mode where moves are not accepted,
6847            perhaps while the mouse button was down. */
6848         return;
6849
6850       case MachinePlaysWhite:
6851         /* User is moving for Black */
6852         if (WhiteOnMove(currentMove)) {
6853             DisplayMoveError(_("It is White's turn"));
6854             return;
6855         }
6856         break;
6857
6858       case MachinePlaysBlack:
6859         /* User is moving for White */
6860         if (!WhiteOnMove(currentMove)) {
6861             DisplayMoveError(_("It is Black's turn"));
6862             return;
6863         }
6864         break;
6865
6866       case PlayFromGameFile:
6867             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6868       case EditGame:
6869       case IcsExamining:
6870       case BeginningOfGame:
6871       case AnalyzeMode:
6872       case Training:
6873         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6874         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6875             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6876             /* User is moving for Black */
6877             if (WhiteOnMove(currentMove)) {
6878                 DisplayMoveError(_("It is White's turn"));
6879                 return;
6880             }
6881         } else {
6882             /* User is moving for White */
6883             if (!WhiteOnMove(currentMove)) {
6884                 DisplayMoveError(_("It is Black's turn"));
6885                 return;
6886             }
6887         }
6888         break;
6889
6890       case IcsPlayingBlack:
6891         /* User is moving for Black */
6892         if (WhiteOnMove(currentMove)) {
6893             if (!appData.premove) {
6894                 DisplayMoveError(_("It is White's turn"));
6895             } else if (toX >= 0 && toY >= 0) {
6896                 premoveToX = toX;
6897                 premoveToY = toY;
6898                 premoveFromX = fromX;
6899                 premoveFromY = fromY;
6900                 premovePromoChar = promoChar;
6901                 gotPremove = 1;
6902                 if (appData.debugMode)
6903                     fprintf(debugFP, "Got premove: fromX %d,"
6904                             "fromY %d, toX %d, toY %d\n",
6905                             fromX, fromY, toX, toY);
6906             }
6907             return;
6908         }
6909         break;
6910
6911       case IcsPlayingWhite:
6912         /* User is moving for White */
6913         if (!WhiteOnMove(currentMove)) {
6914             if (!appData.premove) {
6915                 DisplayMoveError(_("It is Black's turn"));
6916             } else if (toX >= 0 && toY >= 0) {
6917                 premoveToX = toX;
6918                 premoveToY = toY;
6919                 premoveFromX = fromX;
6920                 premoveFromY = fromY;
6921                 premovePromoChar = promoChar;
6922                 gotPremove = 1;
6923                 if (appData.debugMode)
6924                     fprintf(debugFP, "Got premove: fromX %d,"
6925                             "fromY %d, toX %d, toY %d\n",
6926                             fromX, fromY, toX, toY);
6927             }
6928             return;
6929         }
6930         break;
6931
6932       default:
6933         break;
6934
6935       case EditPosition:
6936         /* EditPosition, empty square, or different color piece;
6937            click-click move is possible */
6938         if (toX == -2 || toY == -2) {
6939             boards[0][fromY][fromX] = EmptySquare;
6940             DrawPosition(FALSE, boards[currentMove]);
6941             return;
6942         } else if (toX >= 0 && toY >= 0) {
6943             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6944                 ChessSquare q, p = boards[0][rf][ff];
6945                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6946                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6947                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6948                 if(PieceToChar(q) == '+') gatingPiece = p;
6949             }
6950             boards[0][toY][toX] = boards[0][fromY][fromX];
6951             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6952                 if(boards[0][fromY][0] != EmptySquare) {
6953                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6954                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6955                 }
6956             } else
6957             if(fromX == BOARD_RGHT+1) {
6958                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6959                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6960                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6961                 }
6962             } else
6963             boards[0][fromY][fromX] = gatingPiece;
6964             DrawPosition(FALSE, boards[currentMove]);
6965             return;
6966         }
6967         return;
6968     }
6969
6970     if(toX < 0 || toY < 0) return;
6971     pup = boards[currentMove][toY][toX];
6972
6973     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6974     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6975          if( pup != EmptySquare ) return;
6976          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6977            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6978                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6979            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6980            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6981            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6982            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6983          fromY = DROP_RANK;
6984     }
6985
6986     /* [HGM] always test for legality, to get promotion info */
6987     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6988                                          fromY, fromX, toY, toX, promoChar);
6989
6990     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6991
6992     /* [HGM] but possibly ignore an IllegalMove result */
6993     if (appData.testLegality) {
6994         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6995             DisplayMoveError(_("Illegal move"));
6996             return;
6997         }
6998     }
6999
7000     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7001         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7002              ClearPremoveHighlights(); // was included
7003         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7004         return;
7005     }
7006
7007     if(addToBookFlag) { // adding moves to book
7008         char buf[MSG_SIZ], move[MSG_SIZ];
7009         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7010         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7011         AddBookMove(buf);
7012         addToBookFlag = FALSE;
7013         ClearHighlights();
7014         return;
7015     }
7016
7017     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7018 }
7019
7020 /* Common tail of UserMoveEvent and DropMenuEvent */
7021 int
7022 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7023 {
7024     char *bookHit = 0;
7025
7026     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7027         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7028         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7029         if(WhiteOnMove(currentMove)) {
7030             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7031         } else {
7032             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7033         }
7034     }
7035
7036     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7037        move type in caller when we know the move is a legal promotion */
7038     if(moveType == NormalMove && promoChar)
7039         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7040
7041     /* [HGM] <popupFix> The following if has been moved here from
7042        UserMoveEvent(). Because it seemed to belong here (why not allow
7043        piece drops in training games?), and because it can only be
7044        performed after it is known to what we promote. */
7045     if (gameMode == Training) {
7046       /* compare the move played on the board to the next move in the
7047        * game. If they match, display the move and the opponent's response.
7048        * If they don't match, display an error message.
7049        */
7050       int saveAnimate;
7051       Board testBoard;
7052       CopyBoard(testBoard, boards[currentMove]);
7053       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7054
7055       if (CompareBoards(testBoard, boards[currentMove+1])) {
7056         ForwardInner(currentMove+1);
7057
7058         /* Autoplay the opponent's response.
7059          * if appData.animate was TRUE when Training mode was entered,
7060          * the response will be animated.
7061          */
7062         saveAnimate = appData.animate;
7063         appData.animate = animateTraining;
7064         ForwardInner(currentMove+1);
7065         appData.animate = saveAnimate;
7066
7067         /* check for the end of the game */
7068         if (currentMove >= forwardMostMove) {
7069           gameMode = PlayFromGameFile;
7070           ModeHighlight();
7071           SetTrainingModeOff();
7072           DisplayInformation(_("End of game"));
7073         }
7074       } else {
7075         DisplayError(_("Incorrect move"), 0);
7076       }
7077       return 1;
7078     }
7079
7080   /* Ok, now we know that the move is good, so we can kill
7081      the previous line in Analysis Mode */
7082   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7083                                 && currentMove < forwardMostMove) {
7084     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7085     else forwardMostMove = currentMove;
7086   }
7087
7088   ClearMap();
7089
7090   /* If we need the chess program but it's dead, restart it */
7091   ResurrectChessProgram();
7092
7093   /* A user move restarts a paused game*/
7094   if (pausing)
7095     PauseEvent();
7096
7097   thinkOutput[0] = NULLCHAR;
7098
7099   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7100
7101   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7102     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7103     return 1;
7104   }
7105
7106   if (gameMode == BeginningOfGame) {
7107     if (appData.noChessProgram) {
7108       gameMode = EditGame;
7109       SetGameInfo();
7110     } else {
7111       char buf[MSG_SIZ];
7112       gameMode = MachinePlaysBlack;
7113       StartClocks();
7114       SetGameInfo();
7115       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7116       DisplayTitle(buf);
7117       if (first.sendName) {
7118         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7119         SendToProgram(buf, &first);
7120       }
7121       StartClocks();
7122     }
7123     ModeHighlight();
7124   }
7125
7126   /* Relay move to ICS or chess engine */
7127   if (appData.icsActive) {
7128     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7129         gameMode == IcsExamining) {
7130       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7131         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7132         SendToICS("draw ");
7133         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7134       }
7135       // also send plain move, in case ICS does not understand atomic claims
7136       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7137       ics_user_moved = 1;
7138     }
7139   } else {
7140     if (first.sendTime && (gameMode == BeginningOfGame ||
7141                            gameMode == MachinePlaysWhite ||
7142                            gameMode == MachinePlaysBlack)) {
7143       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7144     }
7145     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7146          // [HGM] book: if program might be playing, let it use book
7147         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7148         first.maybeThinking = TRUE;
7149     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7150         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7151         SendBoard(&first, currentMove+1);
7152         if(second.analyzing) {
7153             if(!second.useSetboard) SendToProgram("undo\n", &second);
7154             SendBoard(&second, currentMove+1);
7155         }
7156     } else {
7157         SendMoveToProgram(forwardMostMove-1, &first);
7158         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7159     }
7160     if (currentMove == cmailOldMove + 1) {
7161       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7162     }
7163   }
7164
7165   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7166
7167   switch (gameMode) {
7168   case EditGame:
7169     if(appData.testLegality)
7170     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7171     case MT_NONE:
7172     case MT_CHECK:
7173       break;
7174     case MT_CHECKMATE:
7175     case MT_STAINMATE:
7176       if (WhiteOnMove(currentMove)) {
7177         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7178       } else {
7179         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7180       }
7181       break;
7182     case MT_STALEMATE:
7183       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7184       break;
7185     }
7186     break;
7187
7188   case MachinePlaysBlack:
7189   case MachinePlaysWhite:
7190     /* disable certain menu options while machine is thinking */
7191     SetMachineThinkingEnables();
7192     break;
7193
7194   default:
7195     break;
7196   }
7197
7198   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7199   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7200
7201   if(bookHit) { // [HGM] book: simulate book reply
7202         static char bookMove[MSG_SIZ]; // a bit generous?
7203
7204         programStats.nodes = programStats.depth = programStats.time =
7205         programStats.score = programStats.got_only_move = 0;
7206         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7207
7208         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7209         strcat(bookMove, bookHit);
7210         HandleMachineMove(bookMove, &first);
7211   }
7212   return 1;
7213 }
7214
7215 void
7216 MarkByFEN(char *fen)
7217 {
7218         int r, f;
7219         if(!appData.markers || !appData.highlightDragging) return;
7220         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7221         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7222         while(*fen) {
7223             int s = 0;
7224             marker[r][f] = 0;
7225             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7226             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7227             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7228             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7229             if(*fen == 'T') marker[r][f++] = 0; else
7230             if(*fen == 'Y') marker[r][f++] = 1; else
7231             if(*fen == 'G') marker[r][f++] = 3; else
7232             if(*fen == 'B') marker[r][f++] = 4; else
7233             if(*fen == 'C') marker[r][f++] = 5; else
7234             if(*fen == 'M') marker[r][f++] = 6; else
7235             if(*fen == 'W') marker[r][f++] = 7; else
7236             if(*fen == 'D') marker[r][f++] = 8; else
7237             if(*fen == 'R') marker[r][f++] = 2; else {
7238                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7239               f += s; fen -= s>0;
7240             }
7241             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7242             if(r < 0) break;
7243             fen++;
7244         }
7245         DrawPosition(TRUE, NULL);
7246 }
7247
7248 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7249
7250 void
7251 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7252 {
7253     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7254     Markers *m = (Markers *) closure;
7255     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7256         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7257                          || kind == WhiteCapturesEnPassant
7258                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7259     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7260 }
7261
7262 static int hoverSavedValid;
7263
7264 void
7265 MarkTargetSquares (int clear)
7266 {
7267   int x, y, sum=0;
7268   if(clear) { // no reason to ever suppress clearing
7269     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7270     hoverSavedValid = 0;
7271     if(!sum) return; // nothing was cleared,no redraw needed
7272   } else {
7273     int capt = 0;
7274     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7275        !appData.testLegality || gameMode == EditPosition) return;
7276     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7277     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7278       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7279       if(capt)
7280       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7281     }
7282   }
7283   DrawPosition(FALSE, NULL);
7284 }
7285
7286 int
7287 Explode (Board board, int fromX, int fromY, int toX, int toY)
7288 {
7289     if(gameInfo.variant == VariantAtomic &&
7290        (board[toY][toX] != EmptySquare ||                     // capture?
7291         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7292                          board[fromY][fromX] == BlackPawn   )
7293       )) {
7294         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7295         return TRUE;
7296     }
7297     return FALSE;
7298 }
7299
7300 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7301
7302 int
7303 CanPromote (ChessSquare piece, int y)
7304 {
7305         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7306         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7307         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7308         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7309            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7310            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7311          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7312         return (piece == BlackPawn && y <= zone ||
7313                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7314                 piece == BlackLance && y == 1 ||
7315                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7316 }
7317
7318 void
7319 HoverEvent (int xPix, int yPix, int x, int y)
7320 {
7321         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7322         int r, f;
7323         if(!first.highlight) return;
7324         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7325         if(x == oldX && y == oldY) return; // only do something if we enter new square
7326         oldFromX = fromX; oldFromY = fromY;
7327         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7328           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7329             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7330           hoverSavedValid = 1;
7331         } else if(oldX != x || oldY != y) {
7332           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7333           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7334           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7335             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7336           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7337             char buf[MSG_SIZ];
7338             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7339             SendToProgram(buf, &first);
7340           }
7341           oldX = x; oldY = y;
7342 //        SetHighlights(fromX, fromY, x, y);
7343         }
7344 }
7345
7346 void ReportClick(char *action, int x, int y)
7347 {
7348         char buf[MSG_SIZ]; // Inform engine of what user does
7349         int r, f;
7350         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7351           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7352         if(!first.highlight || gameMode == EditPosition) return;
7353         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7354         SendToProgram(buf, &first);
7355 }
7356
7357 void
7358 LeftClick (ClickType clickType, int xPix, int yPix)
7359 {
7360     int x, y;
7361     Boolean saveAnimate;
7362     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7363     char promoChoice = NULLCHAR;
7364     ChessSquare piece;
7365     static TimeMark lastClickTime, prevClickTime;
7366
7367     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7368
7369     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7370
7371     if (clickType == Press) ErrorPopDown();
7372     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7373
7374     x = EventToSquare(xPix, BOARD_WIDTH);
7375     y = EventToSquare(yPix, BOARD_HEIGHT);
7376     if (!flipView && y >= 0) {
7377         y = BOARD_HEIGHT - 1 - y;
7378     }
7379     if (flipView && x >= 0) {
7380         x = BOARD_WIDTH - 1 - x;
7381     }
7382
7383     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7384         defaultPromoChoice = promoSweep;
7385         promoSweep = EmptySquare;   // terminate sweep
7386         promoDefaultAltered = TRUE;
7387         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7388     }
7389
7390     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7391         if(clickType == Release) return; // ignore upclick of click-click destination
7392         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7393         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7394         if(gameInfo.holdingsWidth &&
7395                 (WhiteOnMove(currentMove)
7396                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7397                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7398             // click in right holdings, for determining promotion piece
7399             ChessSquare p = boards[currentMove][y][x];
7400             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7401             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7402             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7403                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7404                 fromX = fromY = -1;
7405                 return;
7406             }
7407         }
7408         DrawPosition(FALSE, boards[currentMove]);
7409         return;
7410     }
7411
7412     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7413     if(clickType == Press
7414             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7415               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7416               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7417         return;
7418
7419     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7420         // could be static click on premove from-square: abort premove
7421         gotPremove = 0;
7422         ClearPremoveHighlights();
7423     }
7424
7425     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7426         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7427
7428     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7429         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7430                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7431         defaultPromoChoice = DefaultPromoChoice(side);
7432     }
7433
7434     autoQueen = appData.alwaysPromoteToQueen;
7435
7436     if (fromX == -1) {
7437       int originalY = y;
7438       gatingPiece = EmptySquare;
7439       if (clickType != Press) {
7440         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7441             DragPieceEnd(xPix, yPix); dragging = 0;
7442             DrawPosition(FALSE, NULL);
7443         }
7444         return;
7445       }
7446       doubleClick = FALSE;
7447       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7448         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7449       }
7450       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7451       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7452          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7453          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7454             /* First square */
7455             if (OKToStartUserMove(fromX, fromY)) {
7456                 second = 0;
7457                 ReportClick("lift", x, y);
7458                 MarkTargetSquares(0);
7459                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7460                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7461                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7462                     promoSweep = defaultPromoChoice;
7463                     selectFlag = 0; lastX = xPix; lastY = yPix;
7464                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7465                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7466                 }
7467                 if (appData.highlightDragging) {
7468                     SetHighlights(fromX, fromY, -1, -1);
7469                 } else {
7470                     ClearHighlights();
7471                 }
7472             } else fromX = fromY = -1;
7473             return;
7474         }
7475     }
7476
7477     /* fromX != -1 */
7478     if (clickType == Press && gameMode != EditPosition) {
7479         ChessSquare fromP;
7480         ChessSquare toP;
7481         int frc;
7482
7483         // ignore off-board to clicks
7484         if(y < 0 || x < 0) return;
7485
7486         /* Check if clicking again on the same color piece */
7487         fromP = boards[currentMove][fromY][fromX];
7488         toP = boards[currentMove][y][x];
7489         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7490         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7491            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7492              WhitePawn <= toP && toP <= WhiteKing &&
7493              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7494              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7495             (BlackPawn <= fromP && fromP <= BlackKing &&
7496              BlackPawn <= toP && toP <= BlackKing &&
7497              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7498              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7499             /* Clicked again on same color piece -- changed his mind */
7500             second = (x == fromX && y == fromY);
7501             killX = killY = -1;
7502             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7503                 second = FALSE; // first double-click rather than scond click
7504                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7505             }
7506             promoDefaultAltered = FALSE;
7507             MarkTargetSquares(1);
7508            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7509             if (appData.highlightDragging) {
7510                 SetHighlights(x, y, -1, -1);
7511             } else {
7512                 ClearHighlights();
7513             }
7514             if (OKToStartUserMove(x, y)) {
7515                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7516                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7517                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7518                  gatingPiece = boards[currentMove][fromY][fromX];
7519                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7520                 fromX = x;
7521                 fromY = y; dragging = 1;
7522                 ReportClick("lift", x, y);
7523                 MarkTargetSquares(0);
7524                 DragPieceBegin(xPix, yPix, FALSE);
7525                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7526                     promoSweep = defaultPromoChoice;
7527                     selectFlag = 0; lastX = xPix; lastY = yPix;
7528                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7529                 }
7530             }
7531            }
7532            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7533            second = FALSE;
7534         }
7535         // ignore clicks on holdings
7536         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7537     }
7538
7539     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7540         DragPieceEnd(xPix, yPix); dragging = 0;
7541         if(clearFlag) {
7542             // a deferred attempt to click-click move an empty square on top of a piece
7543             boards[currentMove][y][x] = EmptySquare;
7544             ClearHighlights();
7545             DrawPosition(FALSE, boards[currentMove]);
7546             fromX = fromY = -1; clearFlag = 0;
7547             return;
7548         }
7549         if (appData.animateDragging) {
7550             /* Undo animation damage if any */
7551             DrawPosition(FALSE, NULL);
7552         }
7553         if (second || sweepSelecting) {
7554             /* Second up/down in same square; just abort move */
7555             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7556             second = sweepSelecting = 0;
7557             fromX = fromY = -1;
7558             gatingPiece = EmptySquare;
7559             MarkTargetSquares(1);
7560             ClearHighlights();
7561             gotPremove = 0;
7562             ClearPremoveHighlights();
7563         } else {
7564             /* First upclick in same square; start click-click mode */
7565             SetHighlights(x, y, -1, -1);
7566         }
7567         return;
7568     }
7569
7570     clearFlag = 0;
7571
7572     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7573         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7574         DisplayMessage(_("only marked squares are legal"),"");
7575         DrawPosition(TRUE, NULL);
7576         return; // ignore to-click
7577     }
7578
7579     /* we now have a different from- and (possibly off-board) to-square */
7580     /* Completed move */
7581     if(!sweepSelecting) {
7582         toX = x;
7583         toY = y;
7584     }
7585
7586     piece = boards[currentMove][fromY][fromX];
7587
7588     saveAnimate = appData.animate;
7589     if (clickType == Press) {
7590         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7591         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7592             // must be Edit Position mode with empty-square selected
7593             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7594             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7595             return;
7596         }
7597         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7598             return;
7599         }
7600         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7601             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7602         } else
7603         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7604         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7605           if(appData.sweepSelect) {
7606             promoSweep = defaultPromoChoice;
7607             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7608             selectFlag = 0; lastX = xPix; lastY = yPix;
7609             Sweep(0); // Pawn that is going to promote: preview promotion piece
7610             sweepSelecting = 1;
7611             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7612             MarkTargetSquares(1);
7613           }
7614           return; // promo popup appears on up-click
7615         }
7616         /* Finish clickclick move */
7617         if (appData.animate || appData.highlightLastMove) {
7618             SetHighlights(fromX, fromY, toX, toY);
7619         } else {
7620             ClearHighlights();
7621         }
7622     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7623         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7624         if (appData.animate || appData.highlightLastMove) {
7625             SetHighlights(fromX, fromY, toX, toY);
7626         } else {
7627             ClearHighlights();
7628         }
7629     } else {
7630 #if 0
7631 // [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
7632         /* Finish drag move */
7633         if (appData.highlightLastMove) {
7634             SetHighlights(fromX, fromY, toX, toY);
7635         } else {
7636             ClearHighlights();
7637         }
7638 #endif
7639         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7640         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7641           dragging *= 2;            // flag button-less dragging if we are dragging
7642           MarkTargetSquares(1);
7643           if(x == killX && y == killY) killX = killY = -1; else {
7644             killX = x; killY = y;     //remeber this square as intermediate
7645             ReportClick("put", x, y); // and inform engine
7646             ReportClick("lift", x, y);
7647             MarkTargetSquares(0);
7648             return;
7649           }
7650         }
7651         DragPieceEnd(xPix, yPix); dragging = 0;
7652         /* Don't animate move and drag both */
7653         appData.animate = FALSE;
7654     }
7655
7656     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7657     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7658         ChessSquare piece = boards[currentMove][fromY][fromX];
7659         if(gameMode == EditPosition && piece != EmptySquare &&
7660            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7661             int n;
7662
7663             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7664                 n = PieceToNumber(piece - (int)BlackPawn);
7665                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7666                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7667                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7668             } else
7669             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7670                 n = PieceToNumber(piece);
7671                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7672                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7673                 boards[currentMove][n][BOARD_WIDTH-2]++;
7674             }
7675             boards[currentMove][fromY][fromX] = EmptySquare;
7676         }
7677         ClearHighlights();
7678         fromX = fromY = -1;
7679         MarkTargetSquares(1);
7680         DrawPosition(TRUE, boards[currentMove]);
7681         return;
7682     }
7683
7684     // off-board moves should not be highlighted
7685     if(x < 0 || y < 0) ClearHighlights();
7686     else ReportClick("put", x, y);
7687
7688     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7689
7690     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7691         SetHighlights(fromX, fromY, toX, toY);
7692         MarkTargetSquares(1);
7693         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7694             // [HGM] super: promotion to captured piece selected from holdings
7695             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7696             promotionChoice = TRUE;
7697             // kludge follows to temporarily execute move on display, without promoting yet
7698             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7699             boards[currentMove][toY][toX] = p;
7700             DrawPosition(FALSE, boards[currentMove]);
7701             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7702             boards[currentMove][toY][toX] = q;
7703             DisplayMessage("Click in holdings to choose piece", "");
7704             return;
7705         }
7706         PromotionPopUp(promoChoice);
7707     } else {
7708         int oldMove = currentMove;
7709         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7710         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7711         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7712         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7713            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7714             DrawPosition(TRUE, boards[currentMove]);
7715         MarkTargetSquares(1);
7716         fromX = fromY = -1;
7717     }
7718     appData.animate = saveAnimate;
7719     if (appData.animate || appData.animateDragging) {
7720         /* Undo animation damage if needed */
7721         DrawPosition(FALSE, NULL);
7722     }
7723 }
7724
7725 int
7726 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7727 {   // front-end-free part taken out of PieceMenuPopup
7728     int whichMenu; int xSqr, ySqr;
7729
7730     if(seekGraphUp) { // [HGM] seekgraph
7731         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7732         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7733         return -2;
7734     }
7735
7736     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7737          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7738         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7739         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7740         if(action == Press)   {
7741             originalFlip = flipView;
7742             flipView = !flipView; // temporarily flip board to see game from partners perspective
7743             DrawPosition(TRUE, partnerBoard);
7744             DisplayMessage(partnerStatus, "");
7745             partnerUp = TRUE;
7746         } else if(action == Release) {
7747             flipView = originalFlip;
7748             DrawPosition(TRUE, boards[currentMove]);
7749             partnerUp = FALSE;
7750         }
7751         return -2;
7752     }
7753
7754     xSqr = EventToSquare(x, BOARD_WIDTH);
7755     ySqr = EventToSquare(y, BOARD_HEIGHT);
7756     if (action == Release) {
7757         if(pieceSweep != EmptySquare) {
7758             EditPositionMenuEvent(pieceSweep, toX, toY);
7759             pieceSweep = EmptySquare;
7760         } else UnLoadPV(); // [HGM] pv
7761     }
7762     if (action != Press) return -2; // return code to be ignored
7763     switch (gameMode) {
7764       case IcsExamining:
7765         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7766       case EditPosition:
7767         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7768         if (xSqr < 0 || ySqr < 0) return -1;
7769         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7770         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7771         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7772         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7773         NextPiece(0);
7774         return 2; // grab
7775       case IcsObserving:
7776         if(!appData.icsEngineAnalyze) return -1;
7777       case IcsPlayingWhite:
7778       case IcsPlayingBlack:
7779         if(!appData.zippyPlay) goto noZip;
7780       case AnalyzeMode:
7781       case AnalyzeFile:
7782       case MachinePlaysWhite:
7783       case MachinePlaysBlack:
7784       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7785         if (!appData.dropMenu) {
7786           LoadPV(x, y);
7787           return 2; // flag front-end to grab mouse events
7788         }
7789         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7790            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7791       case EditGame:
7792       noZip:
7793         if (xSqr < 0 || ySqr < 0) return -1;
7794         if (!appData.dropMenu || appData.testLegality &&
7795             gameInfo.variant != VariantBughouse &&
7796             gameInfo.variant != VariantCrazyhouse) return -1;
7797         whichMenu = 1; // drop menu
7798         break;
7799       default:
7800         return -1;
7801     }
7802
7803     if (((*fromX = xSqr) < 0) ||
7804         ((*fromY = ySqr) < 0)) {
7805         *fromX = *fromY = -1;
7806         return -1;
7807     }
7808     if (flipView)
7809       *fromX = BOARD_WIDTH - 1 - *fromX;
7810     else
7811       *fromY = BOARD_HEIGHT - 1 - *fromY;
7812
7813     return whichMenu;
7814 }
7815
7816 void
7817 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7818 {
7819 //    char * hint = lastHint;
7820     FrontEndProgramStats stats;
7821
7822     stats.which = cps == &first ? 0 : 1;
7823     stats.depth = cpstats->depth;
7824     stats.nodes = cpstats->nodes;
7825     stats.score = cpstats->score;
7826     stats.time = cpstats->time;
7827     stats.pv = cpstats->movelist;
7828     stats.hint = lastHint;
7829     stats.an_move_index = 0;
7830     stats.an_move_count = 0;
7831
7832     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7833         stats.hint = cpstats->move_name;
7834         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7835         stats.an_move_count = cpstats->nr_moves;
7836     }
7837
7838     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
7839
7840     SetProgramStats( &stats );
7841 }
7842
7843 void
7844 ClearEngineOutputPane (int which)
7845 {
7846     static FrontEndProgramStats dummyStats;
7847     dummyStats.which = which;
7848     dummyStats.pv = "#";
7849     SetProgramStats( &dummyStats );
7850 }
7851
7852 #define MAXPLAYERS 500
7853
7854 char *
7855 TourneyStandings (int display)
7856 {
7857     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7858     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7859     char result, *p, *names[MAXPLAYERS];
7860
7861     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7862         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7863     names[0] = p = strdup(appData.participants);
7864     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7865
7866     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7867
7868     while(result = appData.results[nr]) {
7869         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7870         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7871         wScore = bScore = 0;
7872         switch(result) {
7873           case '+': wScore = 2; break;
7874           case '-': bScore = 2; break;
7875           case '=': wScore = bScore = 1; break;
7876           case ' ':
7877           case '*': return strdup("busy"); // tourney not finished
7878         }
7879         score[w] += wScore;
7880         score[b] += bScore;
7881         games[w]++;
7882         games[b]++;
7883         nr++;
7884     }
7885     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7886     for(w=0; w<nPlayers; w++) {
7887         bScore = -1;
7888         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7889         ranking[w] = b; points[w] = bScore; score[b] = -2;
7890     }
7891     p = malloc(nPlayers*34+1);
7892     for(w=0; w<nPlayers && w<display; w++)
7893         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7894     free(names[0]);
7895     return p;
7896 }
7897
7898 void
7899 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7900 {       // count all piece types
7901         int p, f, r;
7902         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7903         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7904         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7905                 p = board[r][f];
7906                 pCnt[p]++;
7907                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7908                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7909                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7910                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7911                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7912                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7913         }
7914 }
7915
7916 int
7917 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7918 {
7919         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7920         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7921
7922         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7923         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7924         if(myPawns == 2 && nMine == 3) // KPP
7925             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7926         if(myPawns == 1 && nMine == 2) // KP
7927             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7928         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7929             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7930         if(myPawns) return FALSE;
7931         if(pCnt[WhiteRook+side])
7932             return pCnt[BlackRook-side] ||
7933                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7934                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7935                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7936         if(pCnt[WhiteCannon+side]) {
7937             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7938             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7939         }
7940         if(pCnt[WhiteKnight+side])
7941             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7942         return FALSE;
7943 }
7944
7945 int
7946 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7947 {
7948         VariantClass v = gameInfo.variant;
7949
7950         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7951         if(v == VariantShatranj) return TRUE; // always winnable through baring
7952         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7953         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7954
7955         if(v == VariantXiangqi) {
7956                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7957
7958                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7959                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7960                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7961                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7962                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7963                 if(stale) // we have at least one last-rank P plus perhaps C
7964                     return majors // KPKX
7965                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7966                 else // KCA*E*
7967                     return pCnt[WhiteFerz+side] // KCAK
7968                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7969                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7970                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7971
7972         } else if(v == VariantKnightmate) {
7973                 if(nMine == 1) return FALSE;
7974                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7975         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7976                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7977
7978                 if(nMine == 1) return FALSE; // bare King
7979                 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
7980                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7981                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7982                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7983                 if(pCnt[WhiteKnight+side])
7984                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7985                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7986                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7987                 if(nBishops)
7988                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7989                 if(pCnt[WhiteAlfil+side])
7990                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7991                 if(pCnt[WhiteWazir+side])
7992                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7993         }
7994
7995         return TRUE;
7996 }
7997
7998 int
7999 CompareWithRights (Board b1, Board b2)
8000 {
8001     int rights = 0;
8002     if(!CompareBoards(b1, b2)) return FALSE;
8003     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8004     /* compare castling rights */
8005     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8006            rights++; /* King lost rights, while rook still had them */
8007     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8008         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8009            rights++; /* but at least one rook lost them */
8010     }
8011     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8012            rights++;
8013     if( b1[CASTLING][5] != NoRights ) {
8014         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8015            rights++;
8016     }
8017     return rights == 0;
8018 }
8019
8020 int
8021 Adjudicate (ChessProgramState *cps)
8022 {       // [HGM] some adjudications useful with buggy engines
8023         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8024         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8025         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8026         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8027         int k, drop, count = 0; static int bare = 1;
8028         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8029         Boolean canAdjudicate = !appData.icsActive;
8030
8031         // most tests only when we understand the game, i.e. legality-checking on
8032             if( appData.testLegality )
8033             {   /* [HGM] Some more adjudications for obstinate engines */
8034                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8035                 static int moveCount = 6;
8036                 ChessMove result;
8037                 char *reason = NULL;
8038
8039                 /* Count what is on board. */
8040                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8041
8042                 /* Some material-based adjudications that have to be made before stalemate test */
8043                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8044                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8045                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8046                      if(canAdjudicate && appData.checkMates) {
8047                          if(engineOpponent)
8048                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8049                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8050                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8051                          return 1;
8052                      }
8053                 }
8054
8055                 /* Bare King in Shatranj (loses) or Losers (wins) */
8056                 if( nrW == 1 || nrB == 1) {
8057                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8058                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8059                      if(canAdjudicate && appData.checkMates) {
8060                          if(engineOpponent)
8061                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8062                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8063                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8064                          return 1;
8065                      }
8066                   } else
8067                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8068                   {    /* bare King */
8069                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8070                         if(canAdjudicate && appData.checkMates) {
8071                             /* but only adjudicate if adjudication enabled */
8072                             if(engineOpponent)
8073                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8074                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8075                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8076                             return 1;
8077                         }
8078                   }
8079                 } else bare = 1;
8080
8081
8082             // don't wait for engine to announce game end if we can judge ourselves
8083             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8084               case MT_CHECK:
8085                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8086                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8087                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8088                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8089                             checkCnt++;
8090                         if(checkCnt >= 2) {
8091                             reason = "Xboard adjudication: 3rd check";
8092                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8093                             break;
8094                         }
8095                     }
8096                 }
8097               case MT_NONE:
8098               default:
8099                 break;
8100               case MT_STEALMATE:
8101               case MT_STALEMATE:
8102               case MT_STAINMATE:
8103                 reason = "Xboard adjudication: Stalemate";
8104                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8105                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8106                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8107                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8108                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8109                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8110                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8111                                                                         EP_CHECKMATE : EP_WINS);
8112                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8113                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8114                 }
8115                 break;
8116               case MT_CHECKMATE:
8117                 reason = "Xboard adjudication: Checkmate";
8118                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8119                 if(gameInfo.variant == VariantShogi) {
8120                     if(forwardMostMove > backwardMostMove
8121                        && moveList[forwardMostMove-1][1] == '@'
8122                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8123                         reason = "XBoard adjudication: pawn-drop mate";
8124                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8125                     }
8126                 }
8127                 break;
8128             }
8129
8130                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8131                     case EP_STALEMATE:
8132                         result = GameIsDrawn; break;
8133                     case EP_CHECKMATE:
8134                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8135                     case EP_WINS:
8136                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8137                     default:
8138                         result = EndOfFile;
8139                 }
8140                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8141                     if(engineOpponent)
8142                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8143                     GameEnds( result, reason, GE_XBOARD );
8144                     return 1;
8145                 }
8146
8147                 /* Next absolutely insufficient mating material. */
8148                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8149                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8150                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8151
8152                      /* always flag draws, for judging claims */
8153                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8154
8155                      if(canAdjudicate && appData.materialDraws) {
8156                          /* but only adjudicate them if adjudication enabled */
8157                          if(engineOpponent) {
8158                            SendToProgram("force\n", engineOpponent); // suppress reply
8159                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8160                          }
8161                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8162                          return 1;
8163                      }
8164                 }
8165
8166                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8167                 if(gameInfo.variant == VariantXiangqi ?
8168                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8169                  : nrW + nrB == 4 &&
8170                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8171                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8172                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8173                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8174                    ) ) {
8175                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8176                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8177                           if(engineOpponent) {
8178                             SendToProgram("force\n", engineOpponent); // suppress reply
8179                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8180                           }
8181                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8182                           return 1;
8183                      }
8184                 } else moveCount = 6;
8185             }
8186
8187         // Repetition draws and 50-move rule can be applied independently of legality testing
8188
8189                 /* Check for rep-draws */
8190                 count = 0;
8191                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8192                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8193                 for(k = forwardMostMove-2;
8194                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8195                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8196                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8197                     k-=2)
8198                 {   int rights=0;
8199                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8200                         /* compare castling rights */
8201                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8202                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8203                                 rights++; /* King lost rights, while rook still had them */
8204                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8205                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8206                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8207                                    rights++; /* but at least one rook lost them */
8208                         }
8209                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8210                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8211                                 rights++;
8212                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8213                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8214                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8215                                    rights++;
8216                         }
8217                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8218                             && appData.drawRepeats > 1) {
8219                              /* adjudicate after user-specified nr of repeats */
8220                              int result = GameIsDrawn;
8221                              char *details = "XBoard adjudication: repetition draw";
8222                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8223                                 // [HGM] xiangqi: check for forbidden perpetuals
8224                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8225                                 for(m=forwardMostMove; m>k; m-=2) {
8226                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8227                                         ourPerpetual = 0; // the current mover did not always check
8228                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8229                                         hisPerpetual = 0; // the opponent did not always check
8230                                 }
8231                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8232                                                                         ourPerpetual, hisPerpetual);
8233                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8234                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8235                                     details = "Xboard adjudication: perpetual checking";
8236                                 } else
8237                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8238                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8239                                 } else
8240                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8241                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8242                                         result = BlackWins;
8243                                         details = "Xboard adjudication: repetition";
8244                                     }
8245                                 } else // it must be XQ
8246                                 // Now check for perpetual chases
8247                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8248                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8249                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8250                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8251                                         static char resdet[MSG_SIZ];
8252                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8253                                         details = resdet;
8254                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8255                                     } else
8256                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8257                                         break; // Abort repetition-checking loop.
8258                                 }
8259                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8260                              }
8261                              if(engineOpponent) {
8262                                SendToProgram("force\n", engineOpponent); // suppress reply
8263                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8264                              }
8265                              GameEnds( result, details, GE_XBOARD );
8266                              return 1;
8267                         }
8268                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8269                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8270                     }
8271                 }
8272
8273                 /* Now we test for 50-move draws. Determine ply count */
8274                 count = forwardMostMove;
8275                 /* look for last irreversble move */
8276                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8277                     count--;
8278                 /* if we hit starting position, add initial plies */
8279                 if( count == backwardMostMove )
8280                     count -= initialRulePlies;
8281                 count = forwardMostMove - count;
8282                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8283                         // adjust reversible move counter for checks in Xiangqi
8284                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8285                         if(i < backwardMostMove) i = backwardMostMove;
8286                         while(i <= forwardMostMove) {
8287                                 lastCheck = inCheck; // check evasion does not count
8288                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8289                                 if(inCheck || lastCheck) count--; // check does not count
8290                                 i++;
8291                         }
8292                 }
8293                 if( count >= 100)
8294                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8295                          /* this is used to judge if draw claims are legal */
8296                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8297                          if(engineOpponent) {
8298                            SendToProgram("force\n", engineOpponent); // suppress reply
8299                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8300                          }
8301                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8302                          return 1;
8303                 }
8304
8305                 /* if draw offer is pending, treat it as a draw claim
8306                  * when draw condition present, to allow engines a way to
8307                  * claim draws before making their move to avoid a race
8308                  * condition occurring after their move
8309                  */
8310                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8311                          char *p = NULL;
8312                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8313                              p = "Draw claim: 50-move rule";
8314                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8315                              p = "Draw claim: 3-fold repetition";
8316                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8317                              p = "Draw claim: insufficient mating material";
8318                          if( p != NULL && canAdjudicate) {
8319                              if(engineOpponent) {
8320                                SendToProgram("force\n", engineOpponent); // suppress reply
8321                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8322                              }
8323                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8324                              return 1;
8325                          }
8326                 }
8327
8328                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8329                     if(engineOpponent) {
8330                       SendToProgram("force\n", engineOpponent); // suppress reply
8331                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8332                     }
8333                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8334                     return 1;
8335                 }
8336         return 0;
8337 }
8338
8339 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8340 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8341 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8342
8343 static int
8344 BitbaseProbe ()
8345 {
8346     int pieces[10], squares[10], cnt=0, r, f, res;
8347     static int loaded;
8348     static PPROBE_EGBB probeBB;
8349     if(!appData.testLegality) return 10;
8350     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8351     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8352     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8353     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8354         ChessSquare piece = boards[forwardMostMove][r][f];
8355         int black = (piece >= BlackPawn);
8356         int type = piece - black*BlackPawn;
8357         if(piece == EmptySquare) continue;
8358         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8359         if(type == WhiteKing) type = WhiteQueen + 1;
8360         type = egbbCode[type];
8361         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8362         pieces[cnt] = type + black*6;
8363         if(++cnt > 5) return 11;
8364     }
8365     pieces[cnt] = squares[cnt] = 0;
8366     // probe EGBB
8367     if(loaded == 2) return 13; // loading failed before
8368     if(loaded == 0) {
8369         loaded = 2; // prepare for failure
8370         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8371         HMODULE lib;
8372         PLOAD_EGBB loadBB;
8373         if(!path) return 13; // no egbb installed
8374         strncpy(buf, path + 8, MSG_SIZ);
8375         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8376         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8377         lib = LoadLibrary(buf);
8378         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8379         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8380         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8381         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8382         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8383         loaded = 1; // success!
8384     }
8385     res = probeBB(forwardMostMove & 1, pieces, squares);
8386     return res > 0 ? 1 : res < 0 ? -1 : 0;
8387 }
8388
8389 char *
8390 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8391 {   // [HGM] book: this routine intercepts moves to simulate book replies
8392     char *bookHit = NULL;
8393
8394     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8395         char buf[MSG_SIZ];
8396         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8397         SendToProgram(buf, cps);
8398     }
8399     //first determine if the incoming move brings opponent into his book
8400     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8401         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8402     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8403     if(bookHit != NULL && !cps->bookSuspend) {
8404         // make sure opponent is not going to reply after receiving move to book position
8405         SendToProgram("force\n", cps);
8406         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8407     }
8408     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8409     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8410     // now arrange restart after book miss
8411     if(bookHit) {
8412         // after a book hit we never send 'go', and the code after the call to this routine
8413         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8414         char buf[MSG_SIZ], *move = bookHit;
8415         if(cps->useSAN) {
8416             int fromX, fromY, toX, toY;
8417             char promoChar;
8418             ChessMove moveType;
8419             move = buf + 30;
8420             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8421                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8422                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8423                                     PosFlags(forwardMostMove),
8424                                     fromY, fromX, toY, toX, promoChar, move);
8425             } else {
8426                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8427                 bookHit = NULL;
8428             }
8429         }
8430         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8431         SendToProgram(buf, cps);
8432         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8433     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8434         SendToProgram("go\n", cps);
8435         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8436     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8437         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8438             SendToProgram("go\n", cps);
8439         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8440     }
8441     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8442 }
8443
8444 int
8445 LoadError (char *errmess, ChessProgramState *cps)
8446 {   // unloads engine and switches back to -ncp mode if it was first
8447     if(cps->initDone) return FALSE;
8448     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8449     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8450     cps->pr = NoProc;
8451     if(cps == &first) {
8452         appData.noChessProgram = TRUE;
8453         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8454         gameMode = BeginningOfGame; ModeHighlight();
8455         SetNCPMode();
8456     }
8457     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8458     DisplayMessage("", ""); // erase waiting message
8459     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8460     return TRUE;
8461 }
8462
8463 char *savedMessage;
8464 ChessProgramState *savedState;
8465 void
8466 DeferredBookMove (void)
8467 {
8468         if(savedState->lastPing != savedState->lastPong)
8469                     ScheduleDelayedEvent(DeferredBookMove, 10);
8470         else
8471         HandleMachineMove(savedMessage, savedState);
8472 }
8473
8474 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8475 static ChessProgramState *stalledEngine;
8476 static char stashedInputMove[MSG_SIZ];
8477
8478 void
8479 HandleMachineMove (char *message, ChessProgramState *cps)
8480 {
8481     static char firstLeg[20];
8482     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8483     char realname[MSG_SIZ];
8484     int fromX, fromY, toX, toY;
8485     ChessMove moveType;
8486     char promoChar, roar;
8487     char *p, *pv=buf1;
8488     int machineWhite, oldError;
8489     char *bookHit;
8490
8491     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8492         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8493         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8494             DisplayError(_("Invalid pairing from pairing engine"), 0);
8495             return;
8496         }
8497         pairingReceived = 1;
8498         NextMatchGame();
8499         return; // Skim the pairing messages here.
8500     }
8501
8502     oldError = cps->userError; cps->userError = 0;
8503
8504 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8505     /*
8506      * Kludge to ignore BEL characters
8507      */
8508     while (*message == '\007') message++;
8509
8510     /*
8511      * [HGM] engine debug message: ignore lines starting with '#' character
8512      */
8513     if(cps->debug && *message == '#') return;
8514
8515     /*
8516      * Look for book output
8517      */
8518     if (cps == &first && bookRequested) {
8519         if (message[0] == '\t' || message[0] == ' ') {
8520             /* Part of the book output is here; append it */
8521             strcat(bookOutput, message);
8522             strcat(bookOutput, "  \n");
8523             return;
8524         } else if (bookOutput[0] != NULLCHAR) {
8525             /* All of book output has arrived; display it */
8526             char *p = bookOutput;
8527             while (*p != NULLCHAR) {
8528                 if (*p == '\t') *p = ' ';
8529                 p++;
8530             }
8531             DisplayInformation(bookOutput);
8532             bookRequested = FALSE;
8533             /* Fall through to parse the current output */
8534         }
8535     }
8536
8537     /*
8538      * Look for machine move.
8539      */
8540     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8541         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8542     {
8543         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8544             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8545             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8546             stalledEngine = cps;
8547             if(appData.ponderNextMove) { // bring opponent out of ponder
8548                 if(gameMode == TwoMachinesPlay) {
8549                     if(cps->other->pause)
8550                         PauseEngine(cps->other);
8551                     else
8552                         SendToProgram("easy\n", cps->other);
8553                 }
8554             }
8555             StopClocks();
8556             return;
8557         }
8558
8559         /* This method is only useful on engines that support ping */
8560         if (cps->lastPing != cps->lastPong) {
8561           if (gameMode == BeginningOfGame) {
8562             /* Extra move from before last new; ignore */
8563             if (appData.debugMode) {
8564                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8565             }
8566           } else {
8567             if (appData.debugMode) {
8568                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8569                         cps->which, gameMode);
8570             }
8571
8572             SendToProgram("undo\n", cps);
8573           }
8574           return;
8575         }
8576
8577         switch (gameMode) {
8578           case BeginningOfGame:
8579             /* Extra move from before last reset; ignore */
8580             if (appData.debugMode) {
8581                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8582             }
8583             return;
8584
8585           case EndOfGame:
8586           case IcsIdle:
8587           default:
8588             /* Extra move after we tried to stop.  The mode test is
8589                not a reliable way of detecting this problem, but it's
8590                the best we can do on engines that don't support ping.
8591             */
8592             if (appData.debugMode) {
8593                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8594                         cps->which, gameMode);
8595             }
8596             SendToProgram("undo\n", cps);
8597             return;
8598
8599           case MachinePlaysWhite:
8600           case IcsPlayingWhite:
8601             machineWhite = TRUE;
8602             break;
8603
8604           case MachinePlaysBlack:
8605           case IcsPlayingBlack:
8606             machineWhite = FALSE;
8607             break;
8608
8609           case TwoMachinesPlay:
8610             machineWhite = (cps->twoMachinesColor[0] == 'w');
8611             break;
8612         }
8613         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8614             if (appData.debugMode) {
8615                 fprintf(debugFP,
8616                         "Ignoring move out of turn by %s, gameMode %d"
8617                         ", forwardMost %d\n",
8618                         cps->which, gameMode, forwardMostMove);
8619             }
8620             return;
8621         }
8622
8623         if(cps->alphaRank) AlphaRank(machineMove, 4);
8624
8625         // [HGM] lion: (some very limited) support for Alien protocol
8626         killX = killY = -1;
8627         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8628             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8629             return;
8630         } else if(firstLeg[0]) { // there was a previous leg;
8631             // only support case where same piece makes two step (and don't even test that!)
8632             char buf[20], *p = machineMove+1, *q = buf+1, f;
8633             safeStrCpy(buf, machineMove, 20);
8634             while(isdigit(*q)) q++; // find start of to-square
8635             safeStrCpy(machineMove, firstLeg, 20);
8636             while(isdigit(*p)) p++;
8637             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8638             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8639             firstLeg[0] = NULLCHAR;
8640         }
8641
8642         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8643                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8644             /* Machine move could not be parsed; ignore it. */
8645           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8646                     machineMove, _(cps->which));
8647             DisplayMoveError(buf1);
8648             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8649                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8650             if (gameMode == TwoMachinesPlay) {
8651               GameEnds(machineWhite ? BlackWins : WhiteWins,
8652                        buf1, GE_XBOARD);
8653             }
8654             return;
8655         }
8656
8657         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8658         /* So we have to redo legality test with true e.p. status here,  */
8659         /* to make sure an illegal e.p. capture does not slip through,   */
8660         /* to cause a forfeit on a justified illegal-move complaint      */
8661         /* of the opponent.                                              */
8662         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8663            ChessMove moveType;
8664            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8665                              fromY, fromX, toY, toX, promoChar);
8666             if(moveType == IllegalMove) {
8667               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8668                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8669                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8670                            buf1, GE_XBOARD);
8671                 return;
8672            } else if(!appData.fischerCastling)
8673            /* [HGM] Kludge to handle engines that send FRC-style castling
8674               when they shouldn't (like TSCP-Gothic) */
8675            switch(moveType) {
8676              case WhiteASideCastleFR:
8677              case BlackASideCastleFR:
8678                toX+=2;
8679                currentMoveString[2]++;
8680                break;
8681              case WhiteHSideCastleFR:
8682              case BlackHSideCastleFR:
8683                toX--;
8684                currentMoveString[2]--;
8685                break;
8686              default: ; // nothing to do, but suppresses warning of pedantic compilers
8687            }
8688         }
8689         hintRequested = FALSE;
8690         lastHint[0] = NULLCHAR;
8691         bookRequested = FALSE;
8692         /* Program may be pondering now */
8693         cps->maybeThinking = TRUE;
8694         if (cps->sendTime == 2) cps->sendTime = 1;
8695         if (cps->offeredDraw) cps->offeredDraw--;
8696
8697         /* [AS] Save move info*/
8698         pvInfoList[ forwardMostMove ].score = programStats.score;
8699         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8700         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8701
8702         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8703
8704         /* Test suites abort the 'game' after one move */
8705         if(*appData.finger) {
8706            static FILE *f;
8707            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8708            if(!f) f = fopen(appData.finger, "w");
8709            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8710            else { DisplayFatalError("Bad output file", errno, 0); return; }
8711            free(fen);
8712            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8713         }
8714
8715         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8716         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8717             int count = 0;
8718
8719             while( count < adjudicateLossPlies ) {
8720                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8721
8722                 if( count & 1 ) {
8723                     score = -score; /* Flip score for winning side */
8724                 }
8725 printf("score=%d count=%d\n",score,count);
8726                 if( score > appData.adjudicateLossThreshold ) {
8727                     break;
8728                 }
8729
8730                 count++;
8731             }
8732
8733             if( count >= adjudicateLossPlies ) {
8734                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8735
8736                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8737                     "Xboard adjudication",
8738                     GE_XBOARD );
8739
8740                 return;
8741             }
8742         }
8743
8744         if(Adjudicate(cps)) {
8745             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8746             return; // [HGM] adjudicate: for all automatic game ends
8747         }
8748
8749 #if ZIPPY
8750         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8751             first.initDone) {
8752           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8753                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8754                 SendToICS("draw ");
8755                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8756           }
8757           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8758           ics_user_moved = 1;
8759           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8760                 char buf[3*MSG_SIZ];
8761
8762                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8763                         programStats.score / 100.,
8764                         programStats.depth,
8765                         programStats.time / 100.,
8766                         (unsigned int)programStats.nodes,
8767                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8768                         programStats.movelist);
8769                 SendToICS(buf);
8770           }
8771         }
8772 #endif
8773
8774         /* [AS] Clear stats for next move */
8775         ClearProgramStats();
8776         thinkOutput[0] = NULLCHAR;
8777         hiddenThinkOutputState = 0;
8778
8779         bookHit = NULL;
8780         if (gameMode == TwoMachinesPlay) {
8781             /* [HGM] relaying draw offers moved to after reception of move */
8782             /* and interpreting offer as claim if it brings draw condition */
8783             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8784                 SendToProgram("draw\n", cps->other);
8785             }
8786             if (cps->other->sendTime) {
8787                 SendTimeRemaining(cps->other,
8788                                   cps->other->twoMachinesColor[0] == 'w');
8789             }
8790             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8791             if (firstMove && !bookHit) {
8792                 firstMove = FALSE;
8793                 if (cps->other->useColors) {
8794                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8795                 }
8796                 SendToProgram("go\n", cps->other);
8797             }
8798             cps->other->maybeThinking = TRUE;
8799         }
8800
8801         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8802
8803         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8804
8805         if (!pausing && appData.ringBellAfterMoves) {
8806             if(!roar) RingBell();
8807         }
8808
8809         /*
8810          * Reenable menu items that were disabled while
8811          * machine was thinking
8812          */
8813         if (gameMode != TwoMachinesPlay)
8814             SetUserThinkingEnables();
8815
8816         // [HGM] book: after book hit opponent has received move and is now in force mode
8817         // force the book reply into it, and then fake that it outputted this move by jumping
8818         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8819         if(bookHit) {
8820                 static char bookMove[MSG_SIZ]; // a bit generous?
8821
8822                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8823                 strcat(bookMove, bookHit);
8824                 message = bookMove;
8825                 cps = cps->other;
8826                 programStats.nodes = programStats.depth = programStats.time =
8827                 programStats.score = programStats.got_only_move = 0;
8828                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8829
8830                 if(cps->lastPing != cps->lastPong) {
8831                     savedMessage = message; // args for deferred call
8832                     savedState = cps;
8833                     ScheduleDelayedEvent(DeferredBookMove, 10);
8834                     return;
8835                 }
8836                 goto FakeBookMove;
8837         }
8838
8839         return;
8840     }
8841
8842     /* Set special modes for chess engines.  Later something general
8843      *  could be added here; for now there is just one kludge feature,
8844      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8845      *  when "xboard" is given as an interactive command.
8846      */
8847     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8848         cps->useSigint = FALSE;
8849         cps->useSigterm = FALSE;
8850     }
8851     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8852       ParseFeatures(message+8, cps);
8853       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8854     }
8855
8856     if (!strncmp(message, "setup ", 6) && 
8857         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8858           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8859                                         ) { // [HGM] allow first engine to define opening position
8860       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8861       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8862       *buf = NULLCHAR;
8863       if(sscanf(message, "setup (%s", buf) == 1) {
8864         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8865         ASSIGN(appData.pieceToCharTable, buf);
8866       }
8867       if(startedFromSetupPosition) return;
8868       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8869       if(dummy >= 3) {
8870         while(message[s] && message[s++] != ' ');
8871         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8872            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8873             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8874             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8875           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8876           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8877         }
8878       }
8879       ParseFEN(boards[0], &dummy, message+s, FALSE);
8880       DrawPosition(TRUE, boards[0]);
8881       startedFromSetupPosition = TRUE;
8882       return;
8883     }
8884     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8885      * want this, I was asked to put it in, and obliged.
8886      */
8887     if (!strncmp(message, "setboard ", 9)) {
8888         Board initial_position;
8889
8890         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8891
8892         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8893             DisplayError(_("Bad FEN received from engine"), 0);
8894             return ;
8895         } else {
8896            Reset(TRUE, FALSE);
8897            CopyBoard(boards[0], initial_position);
8898            initialRulePlies = FENrulePlies;
8899            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8900            else gameMode = MachinePlaysBlack;
8901            DrawPosition(FALSE, boards[currentMove]);
8902         }
8903         return;
8904     }
8905
8906     /*
8907      * Look for communication commands
8908      */
8909     if (!strncmp(message, "telluser ", 9)) {
8910         if(message[9] == '\\' && message[10] == '\\')
8911             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8912         PlayTellSound();
8913         DisplayNote(message + 9);
8914         return;
8915     }
8916     if (!strncmp(message, "tellusererror ", 14)) {
8917         cps->userError = 1;
8918         if(message[14] == '\\' && message[15] == '\\')
8919             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8920         PlayTellSound();
8921         DisplayError(message + 14, 0);
8922         return;
8923     }
8924     if (!strncmp(message, "tellopponent ", 13)) {
8925       if (appData.icsActive) {
8926         if (loggedOn) {
8927           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8928           SendToICS(buf1);
8929         }
8930       } else {
8931         DisplayNote(message + 13);
8932       }
8933       return;
8934     }
8935     if (!strncmp(message, "tellothers ", 11)) {
8936       if (appData.icsActive) {
8937         if (loggedOn) {
8938           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8939           SendToICS(buf1);
8940         }
8941       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8942       return;
8943     }
8944     if (!strncmp(message, "tellall ", 8)) {
8945       if (appData.icsActive) {
8946         if (loggedOn) {
8947           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8948           SendToICS(buf1);
8949         }
8950       } else {
8951         DisplayNote(message + 8);
8952       }
8953       return;
8954     }
8955     if (strncmp(message, "warning", 7) == 0) {
8956         /* Undocumented feature, use tellusererror in new code */
8957         DisplayError(message, 0);
8958         return;
8959     }
8960     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8961         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8962         strcat(realname, " query");
8963         AskQuestion(realname, buf2, buf1, cps->pr);
8964         return;
8965     }
8966     /* Commands from the engine directly to ICS.  We don't allow these to be
8967      *  sent until we are logged on. Crafty kibitzes have been known to
8968      *  interfere with the login process.
8969      */
8970     if (loggedOn) {
8971         if (!strncmp(message, "tellics ", 8)) {
8972             SendToICS(message + 8);
8973             SendToICS("\n");
8974             return;
8975         }
8976         if (!strncmp(message, "tellicsnoalias ", 15)) {
8977             SendToICS(ics_prefix);
8978             SendToICS(message + 15);
8979             SendToICS("\n");
8980             return;
8981         }
8982         /* The following are for backward compatibility only */
8983         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8984             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8985             SendToICS(ics_prefix);
8986             SendToICS(message);
8987             SendToICS("\n");
8988             return;
8989         }
8990     }
8991     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8992         if(initPing == cps->lastPong) {
8993             if(gameInfo.variant == VariantUnknown) {
8994                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8995                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8996                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8997             }
8998             initPing = -1;
8999         }
9000         return;
9001     }
9002     if(!strncmp(message, "highlight ", 10)) {
9003         if(appData.testLegality && appData.markers) return;
9004         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9005         return;
9006     }
9007     if(!strncmp(message, "click ", 6)) {
9008         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9009         if(appData.testLegality || !appData.oneClick) return;
9010         sscanf(message+6, "%c%d%c", &f, &y, &c);
9011         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9012         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9013         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9014         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9015         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9016         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9017             LeftClick(Release, lastLeftX, lastLeftY);
9018         controlKey  = (c == ',');
9019         LeftClick(Press, x, y);
9020         LeftClick(Release, x, y);
9021         first.highlight = f;
9022         return;
9023     }
9024     /*
9025      * If the move is illegal, cancel it and redraw the board.
9026      * Also deal with other error cases.  Matching is rather loose
9027      * here to accommodate engines written before the spec.
9028      */
9029     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9030         strncmp(message, "Error", 5) == 0) {
9031         if (StrStr(message, "name") ||
9032             StrStr(message, "rating") || StrStr(message, "?") ||
9033             StrStr(message, "result") || StrStr(message, "board") ||
9034             StrStr(message, "bk") || StrStr(message, "computer") ||
9035             StrStr(message, "variant") || StrStr(message, "hint") ||
9036             StrStr(message, "random") || StrStr(message, "depth") ||
9037             StrStr(message, "accepted")) {
9038             return;
9039         }
9040         if (StrStr(message, "protover")) {
9041           /* Program is responding to input, so it's apparently done
9042              initializing, and this error message indicates it is
9043              protocol version 1.  So we don't need to wait any longer
9044              for it to initialize and send feature commands. */
9045           FeatureDone(cps, 1);
9046           cps->protocolVersion = 1;
9047           return;
9048         }
9049         cps->maybeThinking = FALSE;
9050
9051         if (StrStr(message, "draw")) {
9052             /* Program doesn't have "draw" command */
9053             cps->sendDrawOffers = 0;
9054             return;
9055         }
9056         if (cps->sendTime != 1 &&
9057             (StrStr(message, "time") || StrStr(message, "otim"))) {
9058           /* Program apparently doesn't have "time" or "otim" command */
9059           cps->sendTime = 0;
9060           return;
9061         }
9062         if (StrStr(message, "analyze")) {
9063             cps->analysisSupport = FALSE;
9064             cps->analyzing = FALSE;
9065 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9066             EditGameEvent(); // [HGM] try to preserve loaded game
9067             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9068             DisplayError(buf2, 0);
9069             return;
9070         }
9071         if (StrStr(message, "(no matching move)st")) {
9072           /* Special kludge for GNU Chess 4 only */
9073           cps->stKludge = TRUE;
9074           SendTimeControl(cps, movesPerSession, timeControl,
9075                           timeIncrement, appData.searchDepth,
9076                           searchTime);
9077           return;
9078         }
9079         if (StrStr(message, "(no matching move)sd")) {
9080           /* Special kludge for GNU Chess 4 only */
9081           cps->sdKludge = TRUE;
9082           SendTimeControl(cps, movesPerSession, timeControl,
9083                           timeIncrement, appData.searchDepth,
9084                           searchTime);
9085           return;
9086         }
9087         if (!StrStr(message, "llegal")) {
9088             return;
9089         }
9090         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9091             gameMode == IcsIdle) return;
9092         if (forwardMostMove <= backwardMostMove) return;
9093         if (pausing) PauseEvent();
9094       if(appData.forceIllegal) {
9095             // [HGM] illegal: machine refused move; force position after move into it
9096           SendToProgram("force\n", cps);
9097           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9098                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9099                 // when black is to move, while there might be nothing on a2 or black
9100                 // might already have the move. So send the board as if white has the move.
9101                 // But first we must change the stm of the engine, as it refused the last move
9102                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9103                 if(WhiteOnMove(forwardMostMove)) {
9104                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9105                     SendBoard(cps, forwardMostMove); // kludgeless board
9106                 } else {
9107                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9108                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9109                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9110                 }
9111           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9112             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9113                  gameMode == TwoMachinesPlay)
9114               SendToProgram("go\n", cps);
9115             return;
9116       } else
9117         if (gameMode == PlayFromGameFile) {
9118             /* Stop reading this game file */
9119             gameMode = EditGame;
9120             ModeHighlight();
9121         }
9122         /* [HGM] illegal-move claim should forfeit game when Xboard */
9123         /* only passes fully legal moves                            */
9124         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9125             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9126                                 "False illegal-move claim", GE_XBOARD );
9127             return; // do not take back move we tested as valid
9128         }
9129         currentMove = forwardMostMove-1;
9130         DisplayMove(currentMove-1); /* before DisplayMoveError */
9131         SwitchClocks(forwardMostMove-1); // [HGM] race
9132         DisplayBothClocks();
9133         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9134                 parseList[currentMove], _(cps->which));
9135         DisplayMoveError(buf1);
9136         DrawPosition(FALSE, boards[currentMove]);
9137
9138         SetUserThinkingEnables();
9139         return;
9140     }
9141     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9142         /* Program has a broken "time" command that
9143            outputs a string not ending in newline.
9144            Don't use it. */
9145         cps->sendTime = 0;
9146     }
9147     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9148         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9149             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9150     }
9151
9152     /*
9153      * If chess program startup fails, exit with an error message.
9154      * Attempts to recover here are futile. [HGM] Well, we try anyway
9155      */
9156     if ((StrStr(message, "unknown host") != NULL)
9157         || (StrStr(message, "No remote directory") != NULL)
9158         || (StrStr(message, "not found") != NULL)
9159         || (StrStr(message, "No such file") != NULL)
9160         || (StrStr(message, "can't alloc") != NULL)
9161         || (StrStr(message, "Permission denied") != NULL)) {
9162
9163         cps->maybeThinking = FALSE;
9164         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9165                 _(cps->which), cps->program, cps->host, message);
9166         RemoveInputSource(cps->isr);
9167         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9168             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9169             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9170         }
9171         return;
9172     }
9173
9174     /*
9175      * Look for hint output
9176      */
9177     if (sscanf(message, "Hint: %s", buf1) == 1) {
9178         if (cps == &first && hintRequested) {
9179             hintRequested = FALSE;
9180             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9181                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9182                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9183                                     PosFlags(forwardMostMove),
9184                                     fromY, fromX, toY, toX, promoChar, buf1);
9185                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9186                 DisplayInformation(buf2);
9187             } else {
9188                 /* Hint move could not be parsed!? */
9189               snprintf(buf2, sizeof(buf2),
9190                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9191                         buf1, _(cps->which));
9192                 DisplayError(buf2, 0);
9193             }
9194         } else {
9195           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9196         }
9197         return;
9198     }
9199
9200     /*
9201      * Ignore other messages if game is not in progress
9202      */
9203     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9204         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9205
9206     /*
9207      * look for win, lose, draw, or draw offer
9208      */
9209     if (strncmp(message, "1-0", 3) == 0) {
9210         char *p, *q, *r = "";
9211         p = strchr(message, '{');
9212         if (p) {
9213             q = strchr(p, '}');
9214             if (q) {
9215                 *q = NULLCHAR;
9216                 r = p + 1;
9217             }
9218         }
9219         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9220         return;
9221     } else if (strncmp(message, "0-1", 3) == 0) {
9222         char *p, *q, *r = "";
9223         p = strchr(message, '{');
9224         if (p) {
9225             q = strchr(p, '}');
9226             if (q) {
9227                 *q = NULLCHAR;
9228                 r = p + 1;
9229             }
9230         }
9231         /* Kludge for Arasan 4.1 bug */
9232         if (strcmp(r, "Black resigns") == 0) {
9233             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9234             return;
9235         }
9236         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9237         return;
9238     } else if (strncmp(message, "1/2", 3) == 0) {
9239         char *p, *q, *r = "";
9240         p = strchr(message, '{');
9241         if (p) {
9242             q = strchr(p, '}');
9243             if (q) {
9244                 *q = NULLCHAR;
9245                 r = p + 1;
9246             }
9247         }
9248
9249         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9250         return;
9251
9252     } else if (strncmp(message, "White resign", 12) == 0) {
9253         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9254         return;
9255     } else if (strncmp(message, "Black resign", 12) == 0) {
9256         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9257         return;
9258     } else if (strncmp(message, "White matches", 13) == 0 ||
9259                strncmp(message, "Black matches", 13) == 0   ) {
9260         /* [HGM] ignore GNUShogi noises */
9261         return;
9262     } else if (strncmp(message, "White", 5) == 0 &&
9263                message[5] != '(' &&
9264                StrStr(message, "Black") == NULL) {
9265         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9266         return;
9267     } else if (strncmp(message, "Black", 5) == 0 &&
9268                message[5] != '(') {
9269         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9270         return;
9271     } else if (strcmp(message, "resign") == 0 ||
9272                strcmp(message, "computer resigns") == 0) {
9273         switch (gameMode) {
9274           case MachinePlaysBlack:
9275           case IcsPlayingBlack:
9276             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9277             break;
9278           case MachinePlaysWhite:
9279           case IcsPlayingWhite:
9280             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9281             break;
9282           case TwoMachinesPlay:
9283             if (cps->twoMachinesColor[0] == 'w')
9284               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9285             else
9286               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9287             break;
9288           default:
9289             /* can't happen */
9290             break;
9291         }
9292         return;
9293     } else if (strncmp(message, "opponent mates", 14) == 0) {
9294         switch (gameMode) {
9295           case MachinePlaysBlack:
9296           case IcsPlayingBlack:
9297             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9298             break;
9299           case MachinePlaysWhite:
9300           case IcsPlayingWhite:
9301             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9302             break;
9303           case TwoMachinesPlay:
9304             if (cps->twoMachinesColor[0] == 'w')
9305               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9306             else
9307               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9308             break;
9309           default:
9310             /* can't happen */
9311             break;
9312         }
9313         return;
9314     } else if (strncmp(message, "computer mates", 14) == 0) {
9315         switch (gameMode) {
9316           case MachinePlaysBlack:
9317           case IcsPlayingBlack:
9318             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9319             break;
9320           case MachinePlaysWhite:
9321           case IcsPlayingWhite:
9322             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9323             break;
9324           case TwoMachinesPlay:
9325             if (cps->twoMachinesColor[0] == 'w')
9326               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9327             else
9328               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9329             break;
9330           default:
9331             /* can't happen */
9332             break;
9333         }
9334         return;
9335     } else if (strncmp(message, "checkmate", 9) == 0) {
9336         if (WhiteOnMove(forwardMostMove)) {
9337             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9338         } else {
9339             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9340         }
9341         return;
9342     } else if (strstr(message, "Draw") != NULL ||
9343                strstr(message, "game is a draw") != NULL) {
9344         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9345         return;
9346     } else if (strstr(message, "offer") != NULL &&
9347                strstr(message, "draw") != NULL) {
9348 #if ZIPPY
9349         if (appData.zippyPlay && first.initDone) {
9350             /* Relay offer to ICS */
9351             SendToICS(ics_prefix);
9352             SendToICS("draw\n");
9353         }
9354 #endif
9355         cps->offeredDraw = 2; /* valid until this engine moves twice */
9356         if (gameMode == TwoMachinesPlay) {
9357             if (cps->other->offeredDraw) {
9358                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9359             /* [HGM] in two-machine mode we delay relaying draw offer      */
9360             /* until after we also have move, to see if it is really claim */
9361             }
9362         } else if (gameMode == MachinePlaysWhite ||
9363                    gameMode == MachinePlaysBlack) {
9364           if (userOfferedDraw) {
9365             DisplayInformation(_("Machine accepts your draw offer"));
9366             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9367           } else {
9368             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9369           }
9370         }
9371     }
9372
9373
9374     /*
9375      * Look for thinking output
9376      */
9377     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9378           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9379                                 ) {
9380         int plylev, mvleft, mvtot, curscore, time;
9381         char mvname[MOVE_LEN];
9382         u64 nodes; // [DM]
9383         char plyext;
9384         int ignore = FALSE;
9385         int prefixHint = FALSE;
9386         mvname[0] = NULLCHAR;
9387
9388         switch (gameMode) {
9389           case MachinePlaysBlack:
9390           case IcsPlayingBlack:
9391             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9392             break;
9393           case MachinePlaysWhite:
9394           case IcsPlayingWhite:
9395             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9396             break;
9397           case AnalyzeMode:
9398           case AnalyzeFile:
9399             break;
9400           case IcsObserving: /* [DM] icsEngineAnalyze */
9401             if (!appData.icsEngineAnalyze) ignore = TRUE;
9402             break;
9403           case TwoMachinesPlay:
9404             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9405                 ignore = TRUE;
9406             }
9407             break;
9408           default:
9409             ignore = TRUE;
9410             break;
9411         }
9412
9413         if (!ignore) {
9414             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9415             buf1[0] = NULLCHAR;
9416             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9417                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9418
9419                 if (plyext != ' ' && plyext != '\t') {
9420                     time *= 100;
9421                 }
9422
9423                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9424                 if( cps->scoreIsAbsolute &&
9425                     ( gameMode == MachinePlaysBlack ||
9426                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9427                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9428                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9429                      !WhiteOnMove(currentMove)
9430                     ) )
9431                 {
9432                     curscore = -curscore;
9433                 }
9434
9435                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9436
9437                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9438                         char buf[MSG_SIZ];
9439                         FILE *f;
9440                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9441                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9442                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9443                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9444                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9445                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9446                                 fclose(f);
9447                         }
9448                         else
9449                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9450                           DisplayError(_("failed writing PV"), 0);
9451                 }
9452
9453                 tempStats.depth = plylev;
9454                 tempStats.nodes = nodes;
9455                 tempStats.time = time;
9456                 tempStats.score = curscore;
9457                 tempStats.got_only_move = 0;
9458
9459                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9460                         int ticklen;
9461
9462                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9463                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9464                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9465                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9466                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9467                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9468                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9469                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9470                 }
9471
9472                 /* Buffer overflow protection */
9473                 if (pv[0] != NULLCHAR) {
9474                     if (strlen(pv) >= sizeof(tempStats.movelist)
9475                         && appData.debugMode) {
9476                         fprintf(debugFP,
9477                                 "PV is too long; using the first %u bytes.\n",
9478                                 (unsigned) sizeof(tempStats.movelist) - 1);
9479                     }
9480
9481                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9482                 } else {
9483                     sprintf(tempStats.movelist, " no PV\n");
9484                 }
9485
9486                 if (tempStats.seen_stat) {
9487                     tempStats.ok_to_send = 1;
9488                 }
9489
9490                 if (strchr(tempStats.movelist, '(') != NULL) {
9491                     tempStats.line_is_book = 1;
9492                     tempStats.nr_moves = 0;
9493                     tempStats.moves_left = 0;
9494                 } else {
9495                     tempStats.line_is_book = 0;
9496                 }
9497
9498                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9499                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9500
9501                 SendProgramStatsToFrontend( cps, &tempStats );
9502
9503                 /*
9504                     [AS] Protect the thinkOutput buffer from overflow... this
9505                     is only useful if buf1 hasn't overflowed first!
9506                 */
9507                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9508                          plylev,
9509                          (gameMode == TwoMachinesPlay ?
9510                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9511                          ((double) curscore) / 100.0,
9512                          prefixHint ? lastHint : "",
9513                          prefixHint ? " " : "" );
9514
9515                 if( buf1[0] != NULLCHAR ) {
9516                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9517
9518                     if( strlen(pv) > max_len ) {
9519                         if( appData.debugMode) {
9520                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9521                         }
9522                         pv[max_len+1] = '\0';
9523                     }
9524
9525                     strcat( thinkOutput, pv);
9526                 }
9527
9528                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9529                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9530                     DisplayMove(currentMove - 1);
9531                 }
9532                 return;
9533
9534             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9535                 /* crafty (9.25+) says "(only move) <move>"
9536                  * if there is only 1 legal move
9537                  */
9538                 sscanf(p, "(only move) %s", buf1);
9539                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9540                 sprintf(programStats.movelist, "%s (only move)", buf1);
9541                 programStats.depth = 1;
9542                 programStats.nr_moves = 1;
9543                 programStats.moves_left = 1;
9544                 programStats.nodes = 1;
9545                 programStats.time = 1;
9546                 programStats.got_only_move = 1;
9547
9548                 /* Not really, but we also use this member to
9549                    mean "line isn't going to change" (Crafty
9550                    isn't searching, so stats won't change) */
9551                 programStats.line_is_book = 1;
9552
9553                 SendProgramStatsToFrontend( cps, &programStats );
9554
9555                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9556                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9557                     DisplayMove(currentMove - 1);
9558                 }
9559                 return;
9560             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9561                               &time, &nodes, &plylev, &mvleft,
9562                               &mvtot, mvname) >= 5) {
9563                 /* The stat01: line is from Crafty (9.29+) in response
9564                    to the "." command */
9565                 programStats.seen_stat = 1;
9566                 cps->maybeThinking = TRUE;
9567
9568                 if (programStats.got_only_move || !appData.periodicUpdates)
9569                   return;
9570
9571                 programStats.depth = plylev;
9572                 programStats.time = time;
9573                 programStats.nodes = nodes;
9574                 programStats.moves_left = mvleft;
9575                 programStats.nr_moves = mvtot;
9576                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9577                 programStats.ok_to_send = 1;
9578                 programStats.movelist[0] = '\0';
9579
9580                 SendProgramStatsToFrontend( cps, &programStats );
9581
9582                 return;
9583
9584             } else if (strncmp(message,"++",2) == 0) {
9585                 /* Crafty 9.29+ outputs this */
9586                 programStats.got_fail = 2;
9587                 return;
9588
9589             } else if (strncmp(message,"--",2) == 0) {
9590                 /* Crafty 9.29+ outputs this */
9591                 programStats.got_fail = 1;
9592                 return;
9593
9594             } else if (thinkOutput[0] != NULLCHAR &&
9595                        strncmp(message, "    ", 4) == 0) {
9596                 unsigned message_len;
9597
9598                 p = message;
9599                 while (*p && *p == ' ') p++;
9600
9601                 message_len = strlen( p );
9602
9603                 /* [AS] Avoid buffer overflow */
9604                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9605                     strcat(thinkOutput, " ");
9606                     strcat(thinkOutput, p);
9607                 }
9608
9609                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9610                     strcat(programStats.movelist, " ");
9611                     strcat(programStats.movelist, p);
9612                 }
9613
9614                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9615                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9616                     DisplayMove(currentMove - 1);
9617                 }
9618                 return;
9619             }
9620         }
9621         else {
9622             buf1[0] = NULLCHAR;
9623
9624             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9625                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9626             {
9627                 ChessProgramStats cpstats;
9628
9629                 if (plyext != ' ' && plyext != '\t') {
9630                     time *= 100;
9631                 }
9632
9633                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9634                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9635                     curscore = -curscore;
9636                 }
9637
9638                 cpstats.depth = plylev;
9639                 cpstats.nodes = nodes;
9640                 cpstats.time = time;
9641                 cpstats.score = curscore;
9642                 cpstats.got_only_move = 0;
9643                 cpstats.movelist[0] = '\0';
9644
9645                 if (buf1[0] != NULLCHAR) {
9646                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9647                 }
9648
9649                 cpstats.ok_to_send = 0;
9650                 cpstats.line_is_book = 0;
9651                 cpstats.nr_moves = 0;
9652                 cpstats.moves_left = 0;
9653
9654                 SendProgramStatsToFrontend( cps, &cpstats );
9655             }
9656         }
9657     }
9658 }
9659
9660
9661 /* Parse a game score from the character string "game", and
9662    record it as the history of the current game.  The game
9663    score is NOT assumed to start from the standard position.
9664    The display is not updated in any way.
9665    */
9666 void
9667 ParseGameHistory (char *game)
9668 {
9669     ChessMove moveType;
9670     int fromX, fromY, toX, toY, boardIndex;
9671     char promoChar;
9672     char *p, *q;
9673     char buf[MSG_SIZ];
9674
9675     if (appData.debugMode)
9676       fprintf(debugFP, "Parsing game history: %s\n", game);
9677
9678     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9679     gameInfo.site = StrSave(appData.icsHost);
9680     gameInfo.date = PGNDate();
9681     gameInfo.round = StrSave("-");
9682
9683     /* Parse out names of players */
9684     while (*game == ' ') game++;
9685     p = buf;
9686     while (*game != ' ') *p++ = *game++;
9687     *p = NULLCHAR;
9688     gameInfo.white = StrSave(buf);
9689     while (*game == ' ') game++;
9690     p = buf;
9691     while (*game != ' ' && *game != '\n') *p++ = *game++;
9692     *p = NULLCHAR;
9693     gameInfo.black = StrSave(buf);
9694
9695     /* Parse moves */
9696     boardIndex = blackPlaysFirst ? 1 : 0;
9697     yynewstr(game);
9698     for (;;) {
9699         yyboardindex = boardIndex;
9700         moveType = (ChessMove) Myylex();
9701         switch (moveType) {
9702           case IllegalMove:             /* maybe suicide chess, etc. */
9703   if (appData.debugMode) {
9704     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9705     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9706     setbuf(debugFP, NULL);
9707   }
9708           case WhitePromotion:
9709           case BlackPromotion:
9710           case WhiteNonPromotion:
9711           case BlackNonPromotion:
9712           case NormalMove:
9713           case FirstLeg:
9714           case WhiteCapturesEnPassant:
9715           case BlackCapturesEnPassant:
9716           case WhiteKingSideCastle:
9717           case WhiteQueenSideCastle:
9718           case BlackKingSideCastle:
9719           case BlackQueenSideCastle:
9720           case WhiteKingSideCastleWild:
9721           case WhiteQueenSideCastleWild:
9722           case BlackKingSideCastleWild:
9723           case BlackQueenSideCastleWild:
9724           /* PUSH Fabien */
9725           case WhiteHSideCastleFR:
9726           case WhiteASideCastleFR:
9727           case BlackHSideCastleFR:
9728           case BlackASideCastleFR:
9729           /* POP Fabien */
9730             fromX = currentMoveString[0] - AAA;
9731             fromY = currentMoveString[1] - ONE;
9732             toX = currentMoveString[2] - AAA;
9733             toY = currentMoveString[3] - ONE;
9734             promoChar = currentMoveString[4];
9735             break;
9736           case WhiteDrop:
9737           case BlackDrop:
9738             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9739             fromX = moveType == WhiteDrop ?
9740               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9741             (int) CharToPiece(ToLower(currentMoveString[0]));
9742             fromY = DROP_RANK;
9743             toX = currentMoveString[2] - AAA;
9744             toY = currentMoveString[3] - ONE;
9745             promoChar = NULLCHAR;
9746             break;
9747           case AmbiguousMove:
9748             /* bug? */
9749             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9750   if (appData.debugMode) {
9751     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9752     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9753     setbuf(debugFP, NULL);
9754   }
9755             DisplayError(buf, 0);
9756             return;
9757           case ImpossibleMove:
9758             /* bug? */
9759             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9760   if (appData.debugMode) {
9761     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9762     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9763     setbuf(debugFP, NULL);
9764   }
9765             DisplayError(buf, 0);
9766             return;
9767           case EndOfFile:
9768             if (boardIndex < backwardMostMove) {
9769                 /* Oops, gap.  How did that happen? */
9770                 DisplayError(_("Gap in move list"), 0);
9771                 return;
9772             }
9773             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9774             if (boardIndex > forwardMostMove) {
9775                 forwardMostMove = boardIndex;
9776             }
9777             return;
9778           case ElapsedTime:
9779             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9780                 strcat(parseList[boardIndex-1], " ");
9781                 strcat(parseList[boardIndex-1], yy_text);
9782             }
9783             continue;
9784           case Comment:
9785           case PGNTag:
9786           case NAG:
9787           default:
9788             /* ignore */
9789             continue;
9790           case WhiteWins:
9791           case BlackWins:
9792           case GameIsDrawn:
9793           case GameUnfinished:
9794             if (gameMode == IcsExamining) {
9795                 if (boardIndex < backwardMostMove) {
9796                     /* Oops, gap.  How did that happen? */
9797                     return;
9798                 }
9799                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9800                 return;
9801             }
9802             gameInfo.result = moveType;
9803             p = strchr(yy_text, '{');
9804             if (p == NULL) p = strchr(yy_text, '(');
9805             if (p == NULL) {
9806                 p = yy_text;
9807                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9808             } else {
9809                 q = strchr(p, *p == '{' ? '}' : ')');
9810                 if (q != NULL) *q = NULLCHAR;
9811                 p++;
9812             }
9813             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9814             gameInfo.resultDetails = StrSave(p);
9815             continue;
9816         }
9817         if (boardIndex >= forwardMostMove &&
9818             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9819             backwardMostMove = blackPlaysFirst ? 1 : 0;
9820             return;
9821         }
9822         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9823                                  fromY, fromX, toY, toX, promoChar,
9824                                  parseList[boardIndex]);
9825         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9826         /* currentMoveString is set as a side-effect of yylex */
9827         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9828         strcat(moveList[boardIndex], "\n");
9829         boardIndex++;
9830         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9831         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9832           case MT_NONE:
9833           case MT_STALEMATE:
9834           default:
9835             break;
9836           case MT_CHECK:
9837             if(!IS_SHOGI(gameInfo.variant))
9838                 strcat(parseList[boardIndex - 1], "+");
9839             break;
9840           case MT_CHECKMATE:
9841           case MT_STAINMATE:
9842             strcat(parseList[boardIndex - 1], "#");
9843             break;
9844         }
9845     }
9846 }
9847
9848
9849 /* Apply a move to the given board  */
9850 void
9851 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9852 {
9853   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9854   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9855
9856     /* [HGM] compute & store e.p. status and castling rights for new position */
9857     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9858
9859       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9860       oldEP = (signed char)board[EP_STATUS];
9861       board[EP_STATUS] = EP_NONE;
9862
9863   if (fromY == DROP_RANK) {
9864         /* must be first */
9865         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9866             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9867             return;
9868         }
9869         piece = board[toY][toX] = (ChessSquare) fromX;
9870   } else {
9871 //      ChessSquare victim;
9872       int i;
9873
9874       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9875 //           victim = board[killY][killX],
9876            board[killY][killX] = EmptySquare,
9877            board[EP_STATUS] = EP_CAPTURE;
9878
9879       if( board[toY][toX] != EmptySquare ) {
9880            board[EP_STATUS] = EP_CAPTURE;
9881            if( (fromX != toX || fromY != toY) && // not igui!
9882                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9883                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9884                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9885            }
9886       }
9887
9888       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9889            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9890                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9891       } else
9892       if( board[fromY][fromX] == WhitePawn ) {
9893            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9894                board[EP_STATUS] = EP_PAWN_MOVE;
9895            if( toY-fromY==2) {
9896                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9897                         gameInfo.variant != VariantBerolina || toX < fromX)
9898                       board[EP_STATUS] = toX | berolina;
9899                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9900                         gameInfo.variant != VariantBerolina || toX > fromX)
9901                       board[EP_STATUS] = toX;
9902            }
9903       } else
9904       if( board[fromY][fromX] == BlackPawn ) {
9905            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9906                board[EP_STATUS] = EP_PAWN_MOVE;
9907            if( toY-fromY== -2) {
9908                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9909                         gameInfo.variant != VariantBerolina || toX < fromX)
9910                       board[EP_STATUS] = toX | berolina;
9911                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9912                         gameInfo.variant != VariantBerolina || toX > fromX)
9913                       board[EP_STATUS] = toX;
9914            }
9915        }
9916
9917        for(i=0; i<nrCastlingRights; i++) {
9918            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9919               board[CASTLING][i] == toX   && castlingRank[i] == toY
9920              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9921        }
9922
9923        if(gameInfo.variant == VariantSChess) { // update virginity
9924            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9925            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9926            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9927            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9928        }
9929
9930      if (fromX == toX && fromY == toY) return;
9931
9932      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9933      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9934      if(gameInfo.variant == VariantKnightmate)
9935          king += (int) WhiteUnicorn - (int) WhiteKing;
9936
9937     /* Code added by Tord: */
9938     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9939     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9940         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9941       board[fromY][fromX] = EmptySquare;
9942       board[toY][toX] = EmptySquare;
9943       if((toX > fromX) != (piece == WhiteRook)) {
9944         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9945       } else {
9946         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9947       }
9948     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9949                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9950       board[fromY][fromX] = EmptySquare;
9951       board[toY][toX] = EmptySquare;
9952       if((toX > fromX) != (piece == BlackRook)) {
9953         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9954       } else {
9955         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9956       }
9957     /* End of code added by Tord */
9958
9959     } else if (board[fromY][fromX] == king
9960         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9961         && toY == fromY && toX > fromX+1) {
9962         board[fromY][fromX] = EmptySquare;
9963         board[toY][toX] = king;
9964         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9965         board[fromY][BOARD_RGHT-1] = EmptySquare;
9966     } else if (board[fromY][fromX] == king
9967         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9968                && toY == fromY && toX < fromX-1) {
9969         board[fromY][fromX] = EmptySquare;
9970         board[toY][toX] = king;
9971         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9972         board[fromY][BOARD_LEFT] = EmptySquare;
9973     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9974                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9975                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9976                ) {
9977         /* white pawn promotion */
9978         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9979         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9980             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9981         board[fromY][fromX] = EmptySquare;
9982     } else if ((fromY >= BOARD_HEIGHT>>1)
9983                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9984                && (toX != fromX)
9985                && gameInfo.variant != VariantXiangqi
9986                && gameInfo.variant != VariantBerolina
9987                && (board[fromY][fromX] == WhitePawn)
9988                && (board[toY][toX] == EmptySquare)) {
9989         board[fromY][fromX] = EmptySquare;
9990         board[toY][toX] = WhitePawn;
9991         captured = board[toY - 1][toX];
9992         board[toY - 1][toX] = EmptySquare;
9993     } else if ((fromY == BOARD_HEIGHT-4)
9994                && (toX == fromX)
9995                && gameInfo.variant == VariantBerolina
9996                && (board[fromY][fromX] == WhitePawn)
9997                && (board[toY][toX] == EmptySquare)) {
9998         board[fromY][fromX] = EmptySquare;
9999         board[toY][toX] = WhitePawn;
10000         if(oldEP & EP_BEROLIN_A) {
10001                 captured = board[fromY][fromX-1];
10002                 board[fromY][fromX-1] = EmptySquare;
10003         }else{  captured = board[fromY][fromX+1];
10004                 board[fromY][fromX+1] = EmptySquare;
10005         }
10006     } else if (board[fromY][fromX] == king
10007         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10008                && toY == fromY && toX > fromX+1) {
10009         board[fromY][fromX] = EmptySquare;
10010         board[toY][toX] = king;
10011         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10012         board[fromY][BOARD_RGHT-1] = EmptySquare;
10013     } else if (board[fromY][fromX] == king
10014         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10015                && toY == fromY && toX < fromX-1) {
10016         board[fromY][fromX] = EmptySquare;
10017         board[toY][toX] = king;
10018         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10019         board[fromY][BOARD_LEFT] = EmptySquare;
10020     } else if (fromY == 7 && fromX == 3
10021                && board[fromY][fromX] == BlackKing
10022                && toY == 7 && toX == 5) {
10023         board[fromY][fromX] = EmptySquare;
10024         board[toY][toX] = BlackKing;
10025         board[fromY][7] = EmptySquare;
10026         board[toY][4] = BlackRook;
10027     } else if (fromY == 7 && fromX == 3
10028                && board[fromY][fromX] == BlackKing
10029                && toY == 7 && toX == 1) {
10030         board[fromY][fromX] = EmptySquare;
10031         board[toY][toX] = BlackKing;
10032         board[fromY][0] = EmptySquare;
10033         board[toY][2] = BlackRook;
10034     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10035                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10036                && toY < promoRank && promoChar
10037                ) {
10038         /* black pawn promotion */
10039         board[toY][toX] = CharToPiece(ToLower(promoChar));
10040         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10041             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10042         board[fromY][fromX] = EmptySquare;
10043     } else if ((fromY < BOARD_HEIGHT>>1)
10044                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10045                && (toX != fromX)
10046                && gameInfo.variant != VariantXiangqi
10047                && gameInfo.variant != VariantBerolina
10048                && (board[fromY][fromX] == BlackPawn)
10049                && (board[toY][toX] == EmptySquare)) {
10050         board[fromY][fromX] = EmptySquare;
10051         board[toY][toX] = BlackPawn;
10052         captured = board[toY + 1][toX];
10053         board[toY + 1][toX] = EmptySquare;
10054     } else if ((fromY == 3)
10055                && (toX == fromX)
10056                && gameInfo.variant == VariantBerolina
10057                && (board[fromY][fromX] == BlackPawn)
10058                && (board[toY][toX] == EmptySquare)) {
10059         board[fromY][fromX] = EmptySquare;
10060         board[toY][toX] = BlackPawn;
10061         if(oldEP & EP_BEROLIN_A) {
10062                 captured = board[fromY][fromX-1];
10063                 board[fromY][fromX-1] = EmptySquare;
10064         }else{  captured = board[fromY][fromX+1];
10065                 board[fromY][fromX+1] = EmptySquare;
10066         }
10067     } else {
10068         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10069         board[fromY][fromX] = EmptySquare;
10070         board[toY][toX] = piece;
10071     }
10072   }
10073
10074     if (gameInfo.holdingsWidth != 0) {
10075
10076       /* !!A lot more code needs to be written to support holdings  */
10077       /* [HGM] OK, so I have written it. Holdings are stored in the */
10078       /* penultimate board files, so they are automaticlly stored   */
10079       /* in the game history.                                       */
10080       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10081                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10082         /* Delete from holdings, by decreasing count */
10083         /* and erasing image if necessary            */
10084         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10085         if(p < (int) BlackPawn) { /* white drop */
10086              p -= (int)WhitePawn;
10087                  p = PieceToNumber((ChessSquare)p);
10088              if(p >= gameInfo.holdingsSize) p = 0;
10089              if(--board[p][BOARD_WIDTH-2] <= 0)
10090                   board[p][BOARD_WIDTH-1] = EmptySquare;
10091              if((int)board[p][BOARD_WIDTH-2] < 0)
10092                         board[p][BOARD_WIDTH-2] = 0;
10093         } else {                  /* black drop */
10094              p -= (int)BlackPawn;
10095                  p = PieceToNumber((ChessSquare)p);
10096              if(p >= gameInfo.holdingsSize) p = 0;
10097              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10098                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10099              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10100                         board[BOARD_HEIGHT-1-p][1] = 0;
10101         }
10102       }
10103       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10104           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10105         /* [HGM] holdings: Add to holdings, if holdings exist */
10106         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10107                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10108                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10109         }
10110         p = (int) captured;
10111         if (p >= (int) BlackPawn) {
10112           p -= (int)BlackPawn;
10113           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10114                   /* in Shogi restore piece to its original  first */
10115                   captured = (ChessSquare) (DEMOTED captured);
10116                   p = DEMOTED p;
10117           }
10118           p = PieceToNumber((ChessSquare)p);
10119           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10120           board[p][BOARD_WIDTH-2]++;
10121           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10122         } else {
10123           p -= (int)WhitePawn;
10124           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10125                   captured = (ChessSquare) (DEMOTED captured);
10126                   p = DEMOTED p;
10127           }
10128           p = PieceToNumber((ChessSquare)p);
10129           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10130           board[BOARD_HEIGHT-1-p][1]++;
10131           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10132         }
10133       }
10134     } else if (gameInfo.variant == VariantAtomic) {
10135       if (captured != EmptySquare) {
10136         int y, x;
10137         for (y = toY-1; y <= toY+1; y++) {
10138           for (x = toX-1; x <= toX+1; x++) {
10139             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10140                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10141               board[y][x] = EmptySquare;
10142             }
10143           }
10144         }
10145         board[toY][toX] = EmptySquare;
10146       }
10147     }
10148
10149     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10150         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10151     } else
10152     if(promoChar == '+') {
10153         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10154         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10155         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10156           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10157     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10158         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10159         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10160            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10161         board[toY][toX] = newPiece;
10162     }
10163     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10164                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10165         // [HGM] superchess: take promotion piece out of holdings
10166         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10167         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10168             if(!--board[k][BOARD_WIDTH-2])
10169                 board[k][BOARD_WIDTH-1] = EmptySquare;
10170         } else {
10171             if(!--board[BOARD_HEIGHT-1-k][1])
10172                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10173         }
10174     }
10175 }
10176
10177 /* Updates forwardMostMove */
10178 void
10179 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10180 {
10181     int x = toX, y = toY;
10182     char *s = parseList[forwardMostMove];
10183     ChessSquare p = boards[forwardMostMove][toY][toX];
10184 //    forwardMostMove++; // [HGM] bare: moved downstream
10185
10186     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10187     (void) CoordsToAlgebraic(boards[forwardMostMove],
10188                              PosFlags(forwardMostMove),
10189                              fromY, fromX, y, x, promoChar,
10190                              s);
10191     if(killX >= 0 && killY >= 0)
10192         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10193
10194     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10195         int timeLeft; static int lastLoadFlag=0; int king, piece;
10196         piece = boards[forwardMostMove][fromY][fromX];
10197         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10198         if(gameInfo.variant == VariantKnightmate)
10199             king += (int) WhiteUnicorn - (int) WhiteKing;
10200         if(forwardMostMove == 0) {
10201             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10202                 fprintf(serverMoves, "%s;", UserName());
10203             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10204                 fprintf(serverMoves, "%s;", second.tidy);
10205             fprintf(serverMoves, "%s;", first.tidy);
10206             if(gameMode == MachinePlaysWhite)
10207                 fprintf(serverMoves, "%s;", UserName());
10208             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10209                 fprintf(serverMoves, "%s;", second.tidy);
10210         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10211         lastLoadFlag = loadFlag;
10212         // print base move
10213         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10214         // print castling suffix
10215         if( toY == fromY && piece == king ) {
10216             if(toX-fromX > 1)
10217                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10218             if(fromX-toX >1)
10219                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10220         }
10221         // e.p. suffix
10222         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10223              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10224              boards[forwardMostMove][toY][toX] == EmptySquare
10225              && fromX != toX && fromY != toY)
10226                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10227         // promotion suffix
10228         if(promoChar != NULLCHAR) {
10229             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10230                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10231                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10232             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10233         }
10234         if(!loadFlag) {
10235                 char buf[MOVE_LEN*2], *p; int len;
10236             fprintf(serverMoves, "/%d/%d",
10237                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10238             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10239             else                      timeLeft = blackTimeRemaining/1000;
10240             fprintf(serverMoves, "/%d", timeLeft);
10241                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10242                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10243                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10244                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10245             fprintf(serverMoves, "/%s", buf);
10246         }
10247         fflush(serverMoves);
10248     }
10249
10250     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10251         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10252       return;
10253     }
10254     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10255     if (commentList[forwardMostMove+1] != NULL) {
10256         free(commentList[forwardMostMove+1]);
10257         commentList[forwardMostMove+1] = NULL;
10258     }
10259     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10260     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10261     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10262     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10263     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10264     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10265     adjustedClock = FALSE;
10266     gameInfo.result = GameUnfinished;
10267     if (gameInfo.resultDetails != NULL) {
10268         free(gameInfo.resultDetails);
10269         gameInfo.resultDetails = NULL;
10270     }
10271     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10272                               moveList[forwardMostMove - 1]);
10273     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10274       case MT_NONE:
10275       case MT_STALEMATE:
10276       default:
10277         break;
10278       case MT_CHECK:
10279         if(!IS_SHOGI(gameInfo.variant))
10280             strcat(parseList[forwardMostMove - 1], "+");
10281         break;
10282       case MT_CHECKMATE:
10283       case MT_STAINMATE:
10284         strcat(parseList[forwardMostMove - 1], "#");
10285         break;
10286     }
10287 }
10288
10289 /* Updates currentMove if not pausing */
10290 void
10291 ShowMove (int fromX, int fromY, int toX, int toY)
10292 {
10293     int instant = (gameMode == PlayFromGameFile) ?
10294         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10295     if(appData.noGUI) return;
10296     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10297         if (!instant) {
10298             if (forwardMostMove == currentMove + 1) {
10299                 AnimateMove(boards[forwardMostMove - 1],
10300                             fromX, fromY, toX, toY);
10301             }
10302         }
10303         currentMove = forwardMostMove;
10304     }
10305
10306     killX = killY = -1; // [HGM] lion: used up
10307
10308     if (instant) return;
10309
10310     DisplayMove(currentMove - 1);
10311     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10312             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10313                 SetHighlights(fromX, fromY, toX, toY);
10314             }
10315     }
10316     DrawPosition(FALSE, boards[currentMove]);
10317     DisplayBothClocks();
10318     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10319 }
10320
10321 void
10322 SendEgtPath (ChessProgramState *cps)
10323 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10324         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10325
10326         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10327
10328         while(*p) {
10329             char c, *q = name+1, *r, *s;
10330
10331             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10332             while(*p && *p != ',') *q++ = *p++;
10333             *q++ = ':'; *q = 0;
10334             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10335                 strcmp(name, ",nalimov:") == 0 ) {
10336                 // take nalimov path from the menu-changeable option first, if it is defined
10337               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10338                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10339             } else
10340             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10341                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10342                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10343                 s = r = StrStr(s, ":") + 1; // beginning of path info
10344                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10345                 c = *r; *r = 0;             // temporarily null-terminate path info
10346                     *--q = 0;               // strip of trailig ':' from name
10347                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10348                 *r = c;
10349                 SendToProgram(buf,cps);     // send egtbpath command for this format
10350             }
10351             if(*p == ',') p++; // read away comma to position for next format name
10352         }
10353 }
10354
10355 static int
10356 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10357 {
10358       int width = 8, height = 8, holdings = 0;             // most common sizes
10359       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10360       // correct the deviations default for each variant
10361       if( v == VariantXiangqi ) width = 9,  height = 10;
10362       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10363       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10364       if( v == VariantCapablanca || v == VariantCapaRandom ||
10365           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10366                                 width = 10;
10367       if( v == VariantCourier ) width = 12;
10368       if( v == VariantSuper )                            holdings = 8;
10369       if( v == VariantGreat )   width = 10,              holdings = 8;
10370       if( v == VariantSChess )                           holdings = 7;
10371       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10372       if( v == VariantChuChess) width = 10, height = 10;
10373       if( v == VariantChu )     width = 12, height = 12;
10374       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10375              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10376              holdingsSize >= 0 && holdingsSize != holdings;
10377 }
10378
10379 char variantError[MSG_SIZ];
10380
10381 char *
10382 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10383 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10384       char *p, *variant = VariantName(v);
10385       static char b[MSG_SIZ];
10386       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10387            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10388                                                holdingsSize, variant); // cook up sized variant name
10389            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10390            if(StrStr(list, b) == NULL) {
10391                // specific sized variant not known, check if general sizing allowed
10392                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10393                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10394                             boardWidth, boardHeight, holdingsSize, engine);
10395                    return NULL;
10396                }
10397                /* [HGM] here we really should compare with the maximum supported board size */
10398            }
10399       } else snprintf(b, MSG_SIZ,"%s", variant);
10400       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10401       p = StrStr(list, b);
10402       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10403       if(p == NULL) {
10404           // occurs not at all in list, or only as sub-string
10405           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10406           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10407               int l = strlen(variantError);
10408               char *q;
10409               while(p != list && p[-1] != ',') p--;
10410               q = strchr(p, ',');
10411               if(q) *q = NULLCHAR;
10412               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10413               if(q) *q= ',';
10414           }
10415           return NULL;
10416       }
10417       return b;
10418 }
10419
10420 void
10421 InitChessProgram (ChessProgramState *cps, int setup)
10422 /* setup needed to setup FRC opening position */
10423 {
10424     char buf[MSG_SIZ], *b;
10425     if (appData.noChessProgram) return;
10426     hintRequested = FALSE;
10427     bookRequested = FALSE;
10428
10429     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10430     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10431     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10432     if(cps->memSize) { /* [HGM] memory */
10433       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10434         SendToProgram(buf, cps);
10435     }
10436     SendEgtPath(cps); /* [HGM] EGT */
10437     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10438       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10439         SendToProgram(buf, cps);
10440     }
10441
10442     setboardSpoiledMachineBlack = FALSE;
10443     SendToProgram(cps->initString, cps);
10444     if (gameInfo.variant != VariantNormal &&
10445         gameInfo.variant != VariantLoadable
10446         /* [HGM] also send variant if board size non-standard */
10447         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10448
10449       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10450                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10451       if (b == NULL) {
10452         DisplayFatalError(variantError, 0, 1);
10453         return;
10454       }
10455
10456       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10457       SendToProgram(buf, cps);
10458     }
10459     currentlyInitializedVariant = gameInfo.variant;
10460
10461     /* [HGM] send opening position in FRC to first engine */
10462     if(setup) {
10463           SendToProgram("force\n", cps);
10464           SendBoard(cps, 0);
10465           /* engine is now in force mode! Set flag to wake it up after first move. */
10466           setboardSpoiledMachineBlack = 1;
10467     }
10468
10469     if (cps->sendICS) {
10470       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10471       SendToProgram(buf, cps);
10472     }
10473     cps->maybeThinking = FALSE;
10474     cps->offeredDraw = 0;
10475     if (!appData.icsActive) {
10476         SendTimeControl(cps, movesPerSession, timeControl,
10477                         timeIncrement, appData.searchDepth,
10478                         searchTime);
10479     }
10480     if (appData.showThinking
10481         // [HGM] thinking: four options require thinking output to be sent
10482         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10483                                 ) {
10484         SendToProgram("post\n", cps);
10485     }
10486     SendToProgram("hard\n", cps);
10487     if (!appData.ponderNextMove) {
10488         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10489            it without being sure what state we are in first.  "hard"
10490            is not a toggle, so that one is OK.
10491          */
10492         SendToProgram("easy\n", cps);
10493     }
10494     if (cps->usePing) {
10495       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10496       SendToProgram(buf, cps);
10497     }
10498     cps->initDone = TRUE;
10499     ClearEngineOutputPane(cps == &second);
10500 }
10501
10502
10503 void
10504 ResendOptions (ChessProgramState *cps)
10505 { // send the stored value of the options
10506   int i;
10507   char buf[MSG_SIZ];
10508   Option *opt = cps->option;
10509   for(i=0; i<cps->nrOptions; i++, opt++) {
10510       switch(opt->type) {
10511         case Spin:
10512         case Slider:
10513         case CheckBox:
10514             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10515           break;
10516         case ComboBox:
10517           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10518           break;
10519         default:
10520             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10521           break;
10522         case Button:
10523         case SaveButton:
10524           continue;
10525       }
10526       SendToProgram(buf, cps);
10527   }
10528 }
10529
10530 void
10531 StartChessProgram (ChessProgramState *cps)
10532 {
10533     char buf[MSG_SIZ];
10534     int err;
10535
10536     if (appData.noChessProgram) return;
10537     cps->initDone = FALSE;
10538
10539     if (strcmp(cps->host, "localhost") == 0) {
10540         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10541     } else if (*appData.remoteShell == NULLCHAR) {
10542         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10543     } else {
10544         if (*appData.remoteUser == NULLCHAR) {
10545           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10546                     cps->program);
10547         } else {
10548           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10549                     cps->host, appData.remoteUser, cps->program);
10550         }
10551         err = StartChildProcess(buf, "", &cps->pr);
10552     }
10553
10554     if (err != 0) {
10555       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10556         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10557         if(cps != &first) return;
10558         appData.noChessProgram = TRUE;
10559         ThawUI();
10560         SetNCPMode();
10561 //      DisplayFatalError(buf, err, 1);
10562 //      cps->pr = NoProc;
10563 //      cps->isr = NULL;
10564         return;
10565     }
10566
10567     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10568     if (cps->protocolVersion > 1) {
10569       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10570       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10571         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10572         cps->comboCnt = 0;  //                and values of combo boxes
10573       }
10574       SendToProgram(buf, cps);
10575       if(cps->reload) ResendOptions(cps);
10576     } else {
10577       SendToProgram("xboard\n", cps);
10578     }
10579 }
10580
10581 void
10582 TwoMachinesEventIfReady P((void))
10583 {
10584   static int curMess = 0;
10585   if (first.lastPing != first.lastPong) {
10586     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10587     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10588     return;
10589   }
10590   if (second.lastPing != second.lastPong) {
10591     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10592     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10593     return;
10594   }
10595   DisplayMessage("", ""); curMess = 0;
10596   TwoMachinesEvent();
10597 }
10598
10599 char *
10600 MakeName (char *template)
10601 {
10602     time_t clock;
10603     struct tm *tm;
10604     static char buf[MSG_SIZ];
10605     char *p = buf;
10606     int i;
10607
10608     clock = time((time_t *)NULL);
10609     tm = localtime(&clock);
10610
10611     while(*p++ = *template++) if(p[-1] == '%') {
10612         switch(*template++) {
10613           case 0:   *p = 0; return buf;
10614           case 'Y': i = tm->tm_year+1900; break;
10615           case 'y': i = tm->tm_year-100; break;
10616           case 'M': i = tm->tm_mon+1; break;
10617           case 'd': i = tm->tm_mday; break;
10618           case 'h': i = tm->tm_hour; break;
10619           case 'm': i = tm->tm_min; break;
10620           case 's': i = tm->tm_sec; break;
10621           default:  i = 0;
10622         }
10623         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10624     }
10625     return buf;
10626 }
10627
10628 int
10629 CountPlayers (char *p)
10630 {
10631     int n = 0;
10632     while(p = strchr(p, '\n')) p++, n++; // count participants
10633     return n;
10634 }
10635
10636 FILE *
10637 WriteTourneyFile (char *results, FILE *f)
10638 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10639     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10640     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10641         // create a file with tournament description
10642         fprintf(f, "-participants {%s}\n", appData.participants);
10643         fprintf(f, "-seedBase %d\n", appData.seedBase);
10644         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10645         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10646         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10647         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10648         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10649         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10650         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10651         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10652         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10653         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10654         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10655         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10656         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10657         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10658         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10659         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10660         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10661         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10662         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10663         fprintf(f, "-smpCores %d\n", appData.smpCores);
10664         if(searchTime > 0)
10665                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10666         else {
10667                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10668                 fprintf(f, "-tc %s\n", appData.timeControl);
10669                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10670         }
10671         fprintf(f, "-results \"%s\"\n", results);
10672     }
10673     return f;
10674 }
10675
10676 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10677
10678 void
10679 Substitute (char *participants, int expunge)
10680 {
10681     int i, changed, changes=0, nPlayers=0;
10682     char *p, *q, *r, buf[MSG_SIZ];
10683     if(participants == NULL) return;
10684     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10685     r = p = participants; q = appData.participants;
10686     while(*p && *p == *q) {
10687         if(*p == '\n') r = p+1, nPlayers++;
10688         p++; q++;
10689     }
10690     if(*p) { // difference
10691         while(*p && *p++ != '\n');
10692         while(*q && *q++ != '\n');
10693       changed = nPlayers;
10694         changes = 1 + (strcmp(p, q) != 0);
10695     }
10696     if(changes == 1) { // a single engine mnemonic was changed
10697         q = r; while(*q) nPlayers += (*q++ == '\n');
10698         p = buf; while(*r && (*p = *r++) != '\n') p++;
10699         *p = NULLCHAR;
10700         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10701         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10702         if(mnemonic[i]) { // The substitute is valid
10703             FILE *f;
10704             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10705                 flock(fileno(f), LOCK_EX);
10706                 ParseArgsFromFile(f);
10707                 fseek(f, 0, SEEK_SET);
10708                 FREE(appData.participants); appData.participants = participants;
10709                 if(expunge) { // erase results of replaced engine
10710                     int len = strlen(appData.results), w, b, dummy;
10711                     for(i=0; i<len; i++) {
10712                         Pairing(i, nPlayers, &w, &b, &dummy);
10713                         if((w == changed || b == changed) && appData.results[i] == '*') {
10714                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10715                             fclose(f);
10716                             return;
10717                         }
10718                     }
10719                     for(i=0; i<len; i++) {
10720                         Pairing(i, nPlayers, &w, &b, &dummy);
10721                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10722                     }
10723                 }
10724                 WriteTourneyFile(appData.results, f);
10725                 fclose(f); // release lock
10726                 return;
10727             }
10728         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10729     }
10730     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10731     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10732     free(participants);
10733     return;
10734 }
10735
10736 int
10737 CheckPlayers (char *participants)
10738 {
10739         int i;
10740         char buf[MSG_SIZ], *p;
10741         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10742         while(p = strchr(participants, '\n')) {
10743             *p = NULLCHAR;
10744             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10745             if(!mnemonic[i]) {
10746                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10747                 *p = '\n';
10748                 DisplayError(buf, 0);
10749                 return 1;
10750             }
10751             *p = '\n';
10752             participants = p + 1;
10753         }
10754         return 0;
10755 }
10756
10757 int
10758 CreateTourney (char *name)
10759 {
10760         FILE *f;
10761         if(matchMode && strcmp(name, appData.tourneyFile)) {
10762              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10763         }
10764         if(name[0] == NULLCHAR) {
10765             if(appData.participants[0])
10766                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10767             return 0;
10768         }
10769         f = fopen(name, "r");
10770         if(f) { // file exists
10771             ASSIGN(appData.tourneyFile, name);
10772             ParseArgsFromFile(f); // parse it
10773         } else {
10774             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10775             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10776                 DisplayError(_("Not enough participants"), 0);
10777                 return 0;
10778             }
10779             if(CheckPlayers(appData.participants)) return 0;
10780             ASSIGN(appData.tourneyFile, name);
10781             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10782             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10783         }
10784         fclose(f);
10785         appData.noChessProgram = FALSE;
10786         appData.clockMode = TRUE;
10787         SetGNUMode();
10788         return 1;
10789 }
10790
10791 int
10792 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10793 {
10794     char buf[MSG_SIZ], *p, *q;
10795     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10796     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10797     skip = !all && group[0]; // if group requested, we start in skip mode
10798     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10799         p = names; q = buf; header = 0;
10800         while(*p && *p != '\n') *q++ = *p++;
10801         *q = 0;
10802         if(*p == '\n') p++;
10803         if(buf[0] == '#') {
10804             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10805             depth++; // we must be entering a new group
10806             if(all) continue; // suppress printing group headers when complete list requested
10807             header = 1;
10808             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10809         }
10810         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10811         if(engineList[i]) free(engineList[i]);
10812         engineList[i] = strdup(buf);
10813         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10814         if(engineMnemonic[i]) free(engineMnemonic[i]);
10815         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10816             strcat(buf, " (");
10817             sscanf(q + 8, "%s", buf + strlen(buf));
10818             strcat(buf, ")");
10819         }
10820         engineMnemonic[i] = strdup(buf);
10821         i++;
10822     }
10823     engineList[i] = engineMnemonic[i] = NULL;
10824     return i;
10825 }
10826
10827 // following implemented as macro to avoid type limitations
10828 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10829
10830 void
10831 SwapEngines (int n)
10832 {   // swap settings for first engine and other engine (so far only some selected options)
10833     int h;
10834     char *p;
10835     if(n == 0) return;
10836     SWAP(directory, p)
10837     SWAP(chessProgram, p)
10838     SWAP(isUCI, h)
10839     SWAP(hasOwnBookUCI, h)
10840     SWAP(protocolVersion, h)
10841     SWAP(reuse, h)
10842     SWAP(scoreIsAbsolute, h)
10843     SWAP(timeOdds, h)
10844     SWAP(logo, p)
10845     SWAP(pgnName, p)
10846     SWAP(pvSAN, h)
10847     SWAP(engOptions, p)
10848     SWAP(engInitString, p)
10849     SWAP(computerString, p)
10850     SWAP(features, p)
10851     SWAP(fenOverride, p)
10852     SWAP(NPS, h)
10853     SWAP(accumulateTC, h)
10854     SWAP(drawDepth, h)
10855     SWAP(host, p)
10856     SWAP(pseudo, h)
10857 }
10858
10859 int
10860 GetEngineLine (char *s, int n)
10861 {
10862     int i;
10863     char buf[MSG_SIZ];
10864     extern char *icsNames;
10865     if(!s || !*s) return 0;
10866     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10867     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10868     if(!mnemonic[i]) return 0;
10869     if(n == 11) return 1; // just testing if there was a match
10870     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10871     if(n == 1) SwapEngines(n);
10872     ParseArgsFromString(buf);
10873     if(n == 1) SwapEngines(n);
10874     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10875         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10876         ParseArgsFromString(buf);
10877     }
10878     return 1;
10879 }
10880
10881 int
10882 SetPlayer (int player, char *p)
10883 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10884     int i;
10885     char buf[MSG_SIZ], *engineName;
10886     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10887     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10888     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10889     if(mnemonic[i]) {
10890         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10891         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10892         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10893         ParseArgsFromString(buf);
10894     } else { // no engine with this nickname is installed!
10895         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10896         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10897         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10898         ModeHighlight();
10899         DisplayError(buf, 0);
10900         return 0;
10901     }
10902     free(engineName);
10903     return i;
10904 }
10905
10906 char *recentEngines;
10907
10908 void
10909 RecentEngineEvent (int nr)
10910 {
10911     int n;
10912 //    SwapEngines(1); // bump first to second
10913 //    ReplaceEngine(&second, 1); // and load it there
10914     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10915     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10916     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10917         ReplaceEngine(&first, 0);
10918         FloatToFront(&appData.recentEngineList, command[n]);
10919     }
10920 }
10921
10922 int
10923 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10924 {   // determine players from game number
10925     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10926
10927     if(appData.tourneyType == 0) {
10928         roundsPerCycle = (nPlayers - 1) | 1;
10929         pairingsPerRound = nPlayers / 2;
10930     } else if(appData.tourneyType > 0) {
10931         roundsPerCycle = nPlayers - appData.tourneyType;
10932         pairingsPerRound = appData.tourneyType;
10933     }
10934     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10935     gamesPerCycle = gamesPerRound * roundsPerCycle;
10936     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10937     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10938     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10939     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10940     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10941     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10942
10943     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10944     if(appData.roundSync) *syncInterval = gamesPerRound;
10945
10946     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10947
10948     if(appData.tourneyType == 0) {
10949         if(curPairing == (nPlayers-1)/2 ) {
10950             *whitePlayer = curRound;
10951             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10952         } else {
10953             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10954             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10955             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10956             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10957         }
10958     } else if(appData.tourneyType > 1) {
10959         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10960         *whitePlayer = curRound + appData.tourneyType;
10961     } else if(appData.tourneyType > 0) {
10962         *whitePlayer = curPairing;
10963         *blackPlayer = curRound + appData.tourneyType;
10964     }
10965
10966     // take care of white/black alternation per round.
10967     // For cycles and games this is already taken care of by default, derived from matchGame!
10968     return curRound & 1;
10969 }
10970
10971 int
10972 NextTourneyGame (int nr, int *swapColors)
10973 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10974     char *p, *q;
10975     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10976     FILE *tf;
10977     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10978     tf = fopen(appData.tourneyFile, "r");
10979     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10980     ParseArgsFromFile(tf); fclose(tf);
10981     InitTimeControls(); // TC might be altered from tourney file
10982
10983     nPlayers = CountPlayers(appData.participants); // count participants
10984     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10985     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10986
10987     if(syncInterval) {
10988         p = q = appData.results;
10989         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10990         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10991             DisplayMessage(_("Waiting for other game(s)"),"");
10992             waitingForGame = TRUE;
10993             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10994             return 0;
10995         }
10996         waitingForGame = FALSE;
10997     }
10998
10999     if(appData.tourneyType < 0) {
11000         if(nr>=0 && !pairingReceived) {
11001             char buf[1<<16];
11002             if(pairing.pr == NoProc) {
11003                 if(!appData.pairingEngine[0]) {
11004                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11005                     return 0;
11006                 }
11007                 StartChessProgram(&pairing); // starts the pairing engine
11008             }
11009             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11010             SendToProgram(buf, &pairing);
11011             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11012             SendToProgram(buf, &pairing);
11013             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11014         }
11015         pairingReceived = 0;                              // ... so we continue here
11016         *swapColors = 0;
11017         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11018         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11019         matchGame = 1; roundNr = nr / syncInterval + 1;
11020     }
11021
11022     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11023
11024     // redefine engines, engine dir, etc.
11025     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11026     if(first.pr == NoProc) {
11027       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11028       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11029     }
11030     if(second.pr == NoProc) {
11031       SwapEngines(1);
11032       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11033       SwapEngines(1);         // and make that valid for second engine by swapping
11034       InitEngine(&second, 1);
11035     }
11036     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11037     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11038     return OK;
11039 }
11040
11041 void
11042 NextMatchGame ()
11043 {   // performs game initialization that does not invoke engines, and then tries to start the game
11044     int res, firstWhite, swapColors = 0;
11045     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11046     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
11047         char buf[MSG_SIZ];
11048         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11049         if(strcmp(buf, currentDebugFile)) { // name has changed
11050             FILE *f = fopen(buf, "w");
11051             if(f) { // if opening the new file failed, just keep using the old one
11052                 ASSIGN(currentDebugFile, buf);
11053                 fclose(debugFP);
11054                 debugFP = f;
11055             }
11056             if(appData.serverFileName) {
11057                 if(serverFP) fclose(serverFP);
11058                 serverFP = fopen(appData.serverFileName, "w");
11059                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11060                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11061             }
11062         }
11063     }
11064     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11065     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11066     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11067     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11068     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11069     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11070     Reset(FALSE, first.pr != NoProc);
11071     res = LoadGameOrPosition(matchGame); // setup game
11072     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11073     if(!res) return; // abort when bad game/pos file
11074     TwoMachinesEvent();
11075 }
11076
11077 void
11078 UserAdjudicationEvent (int result)
11079 {
11080     ChessMove gameResult = GameIsDrawn;
11081
11082     if( result > 0 ) {
11083         gameResult = WhiteWins;
11084     }
11085     else if( result < 0 ) {
11086         gameResult = BlackWins;
11087     }
11088
11089     if( gameMode == TwoMachinesPlay ) {
11090         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11091     }
11092 }
11093
11094
11095 // [HGM] save: calculate checksum of game to make games easily identifiable
11096 int
11097 StringCheckSum (char *s)
11098 {
11099         int i = 0;
11100         if(s==NULL) return 0;
11101         while(*s) i = i*259 + *s++;
11102         return i;
11103 }
11104
11105 int
11106 GameCheckSum ()
11107 {
11108         int i, sum=0;
11109         for(i=backwardMostMove; i<forwardMostMove; i++) {
11110                 sum += pvInfoList[i].depth;
11111                 sum += StringCheckSum(parseList[i]);
11112                 sum += StringCheckSum(commentList[i]);
11113                 sum *= 261;
11114         }
11115         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11116         return sum + StringCheckSum(commentList[i]);
11117 } // end of save patch
11118
11119 void
11120 GameEnds (ChessMove result, char *resultDetails, int whosays)
11121 {
11122     GameMode nextGameMode;
11123     int isIcsGame;
11124     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11125
11126     if(endingGame) return; /* [HGM] crash: forbid recursion */
11127     endingGame = 1;
11128     if(twoBoards) { // [HGM] dual: switch back to one board
11129         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11130         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11131     }
11132     if (appData.debugMode) {
11133       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11134               result, resultDetails ? resultDetails : "(null)", whosays);
11135     }
11136
11137     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11138
11139     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11140
11141     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11142         /* If we are playing on ICS, the server decides when the
11143            game is over, but the engine can offer to draw, claim
11144            a draw, or resign.
11145          */
11146 #if ZIPPY
11147         if (appData.zippyPlay && first.initDone) {
11148             if (result == GameIsDrawn) {
11149                 /* In case draw still needs to be claimed */
11150                 SendToICS(ics_prefix);
11151                 SendToICS("draw\n");
11152             } else if (StrCaseStr(resultDetails, "resign")) {
11153                 SendToICS(ics_prefix);
11154                 SendToICS("resign\n");
11155             }
11156         }
11157 #endif
11158         endingGame = 0; /* [HGM] crash */
11159         return;
11160     }
11161
11162     /* If we're loading the game from a file, stop */
11163     if (whosays == GE_FILE) {
11164       (void) StopLoadGameTimer();
11165       gameFileFP = NULL;
11166     }
11167
11168     /* Cancel draw offers */
11169     first.offeredDraw = second.offeredDraw = 0;
11170
11171     /* If this is an ICS game, only ICS can really say it's done;
11172        if not, anyone can. */
11173     isIcsGame = (gameMode == IcsPlayingWhite ||
11174                  gameMode == IcsPlayingBlack ||
11175                  gameMode == IcsObserving    ||
11176                  gameMode == IcsExamining);
11177
11178     if (!isIcsGame || whosays == GE_ICS) {
11179         /* OK -- not an ICS game, or ICS said it was done */
11180         StopClocks();
11181         if (!isIcsGame && !appData.noChessProgram)
11182           SetUserThinkingEnables();
11183
11184         /* [HGM] if a machine claims the game end we verify this claim */
11185         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11186             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11187                 char claimer;
11188                 ChessMove trueResult = (ChessMove) -1;
11189
11190                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11191                                             first.twoMachinesColor[0] :
11192                                             second.twoMachinesColor[0] ;
11193
11194                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11195                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11196                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11197                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11198                 } else
11199                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11200                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11201                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11202                 } else
11203                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11204                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11205                 }
11206
11207                 // now verify win claims, but not in drop games, as we don't understand those yet
11208                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11209                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11210                     (result == WhiteWins && claimer == 'w' ||
11211                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11212                       if (appData.debugMode) {
11213                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11214                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11215                       }
11216                       if(result != trueResult) {
11217                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11218                               result = claimer == 'w' ? BlackWins : WhiteWins;
11219                               resultDetails = buf;
11220                       }
11221                 } else
11222                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11223                     && (forwardMostMove <= backwardMostMove ||
11224                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11225                         (claimer=='b')==(forwardMostMove&1))
11226                                                                                   ) {
11227                       /* [HGM] verify: draws that were not flagged are false claims */
11228                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11229                       result = claimer == 'w' ? BlackWins : WhiteWins;
11230                       resultDetails = buf;
11231                 }
11232                 /* (Claiming a loss is accepted no questions asked!) */
11233             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11234                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11235                 result = GameUnfinished;
11236                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11237             }
11238             /* [HGM] bare: don't allow bare King to win */
11239             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11240                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11241                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11242                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11243                && result != GameIsDrawn)
11244             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11245                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11246                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11247                         if(p >= 0 && p <= (int)WhiteKing) k++;
11248                 }
11249                 if (appData.debugMode) {
11250                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11251                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11252                 }
11253                 if(k <= 1) {
11254                         result = GameIsDrawn;
11255                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11256                         resultDetails = buf;
11257                 }
11258             }
11259         }
11260
11261
11262         if(serverMoves != NULL && !loadFlag) { char c = '=';
11263             if(result==WhiteWins) c = '+';
11264             if(result==BlackWins) c = '-';
11265             if(resultDetails != NULL)
11266                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11267         }
11268         if (resultDetails != NULL) {
11269             gameInfo.result = result;
11270             gameInfo.resultDetails = StrSave(resultDetails);
11271
11272             /* display last move only if game was not loaded from file */
11273             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11274                 DisplayMove(currentMove - 1);
11275
11276             if (forwardMostMove != 0) {
11277                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11278                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11279                                                                 ) {
11280                     if (*appData.saveGameFile != NULLCHAR) {
11281                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11282                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11283                         else
11284                         SaveGameToFile(appData.saveGameFile, TRUE);
11285                     } else if (appData.autoSaveGames) {
11286                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11287                     }
11288                     if (*appData.savePositionFile != NULLCHAR) {
11289                         SavePositionToFile(appData.savePositionFile);
11290                     }
11291                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11292                 }
11293             }
11294
11295             /* Tell program how game ended in case it is learning */
11296             /* [HGM] Moved this to after saving the PGN, just in case */
11297             /* engine died and we got here through time loss. In that */
11298             /* case we will get a fatal error writing the pipe, which */
11299             /* would otherwise lose us the PGN.                       */
11300             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11301             /* output during GameEnds should never be fatal anymore   */
11302             if (gameMode == MachinePlaysWhite ||
11303                 gameMode == MachinePlaysBlack ||
11304                 gameMode == TwoMachinesPlay ||
11305                 gameMode == IcsPlayingWhite ||
11306                 gameMode == IcsPlayingBlack ||
11307                 gameMode == BeginningOfGame) {
11308                 char buf[MSG_SIZ];
11309                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11310                         resultDetails);
11311                 if (first.pr != NoProc) {
11312                     SendToProgram(buf, &first);
11313                 }
11314                 if (second.pr != NoProc &&
11315                     gameMode == TwoMachinesPlay) {
11316                     SendToProgram(buf, &second);
11317                 }
11318             }
11319         }
11320
11321         if (appData.icsActive) {
11322             if (appData.quietPlay &&
11323                 (gameMode == IcsPlayingWhite ||
11324                  gameMode == IcsPlayingBlack)) {
11325                 SendToICS(ics_prefix);
11326                 SendToICS("set shout 1\n");
11327             }
11328             nextGameMode = IcsIdle;
11329             ics_user_moved = FALSE;
11330             /* clean up premove.  It's ugly when the game has ended and the
11331              * premove highlights are still on the board.
11332              */
11333             if (gotPremove) {
11334               gotPremove = FALSE;
11335               ClearPremoveHighlights();
11336               DrawPosition(FALSE, boards[currentMove]);
11337             }
11338             if (whosays == GE_ICS) {
11339                 switch (result) {
11340                 case WhiteWins:
11341                     if (gameMode == IcsPlayingWhite)
11342                         PlayIcsWinSound();
11343                     else if(gameMode == IcsPlayingBlack)
11344                         PlayIcsLossSound();
11345                     break;
11346                 case BlackWins:
11347                     if (gameMode == IcsPlayingBlack)
11348                         PlayIcsWinSound();
11349                     else if(gameMode == IcsPlayingWhite)
11350                         PlayIcsLossSound();
11351                     break;
11352                 case GameIsDrawn:
11353                     PlayIcsDrawSound();
11354                     break;
11355                 default:
11356                     PlayIcsUnfinishedSound();
11357                 }
11358             }
11359             if(appData.quitNext) { ExitEvent(0); return; }
11360         } else if (gameMode == EditGame ||
11361                    gameMode == PlayFromGameFile ||
11362                    gameMode == AnalyzeMode ||
11363                    gameMode == AnalyzeFile) {
11364             nextGameMode = gameMode;
11365         } else {
11366             nextGameMode = EndOfGame;
11367         }
11368         pausing = FALSE;
11369         ModeHighlight();
11370     } else {
11371         nextGameMode = gameMode;
11372     }
11373
11374     if (appData.noChessProgram) {
11375         gameMode = nextGameMode;
11376         ModeHighlight();
11377         endingGame = 0; /* [HGM] crash */
11378         return;
11379     }
11380
11381     if (first.reuse) {
11382         /* Put first chess program into idle state */
11383         if (first.pr != NoProc &&
11384             (gameMode == MachinePlaysWhite ||
11385              gameMode == MachinePlaysBlack ||
11386              gameMode == TwoMachinesPlay ||
11387              gameMode == IcsPlayingWhite ||
11388              gameMode == IcsPlayingBlack ||
11389              gameMode == BeginningOfGame)) {
11390             SendToProgram("force\n", &first);
11391             if (first.usePing) {
11392               char buf[MSG_SIZ];
11393               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11394               SendToProgram(buf, &first);
11395             }
11396         }
11397     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11398         /* Kill off first chess program */
11399         if (first.isr != NULL)
11400           RemoveInputSource(first.isr);
11401         first.isr = NULL;
11402
11403         if (first.pr != NoProc) {
11404             ExitAnalyzeMode();
11405             DoSleep( appData.delayBeforeQuit );
11406             SendToProgram("quit\n", &first);
11407             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11408             first.reload = TRUE;
11409         }
11410         first.pr = NoProc;
11411     }
11412     if (second.reuse) {
11413         /* Put second chess program into idle state */
11414         if (second.pr != NoProc &&
11415             gameMode == TwoMachinesPlay) {
11416             SendToProgram("force\n", &second);
11417             if (second.usePing) {
11418               char buf[MSG_SIZ];
11419               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11420               SendToProgram(buf, &second);
11421             }
11422         }
11423     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11424         /* Kill off second chess program */
11425         if (second.isr != NULL)
11426           RemoveInputSource(second.isr);
11427         second.isr = NULL;
11428
11429         if (second.pr != NoProc) {
11430             DoSleep( appData.delayBeforeQuit );
11431             SendToProgram("quit\n", &second);
11432             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11433             second.reload = TRUE;
11434         }
11435         second.pr = NoProc;
11436     }
11437
11438     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11439         char resChar = '=';
11440         switch (result) {
11441         case WhiteWins:
11442           resChar = '+';
11443           if (first.twoMachinesColor[0] == 'w') {
11444             first.matchWins++;
11445           } else {
11446             second.matchWins++;
11447           }
11448           break;
11449         case BlackWins:
11450           resChar = '-';
11451           if (first.twoMachinesColor[0] == 'b') {
11452             first.matchWins++;
11453           } else {
11454             second.matchWins++;
11455           }
11456           break;
11457         case GameUnfinished:
11458           resChar = ' ';
11459         default:
11460           break;
11461         }
11462
11463         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11464         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11465             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11466             ReserveGame(nextGame, resChar); // sets nextGame
11467             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11468             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11469         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11470
11471         if (nextGame <= appData.matchGames && !abortMatch) {
11472             gameMode = nextGameMode;
11473             matchGame = nextGame; // this will be overruled in tourney mode!
11474             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11475             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11476             endingGame = 0; /* [HGM] crash */
11477             return;
11478         } else {
11479             gameMode = nextGameMode;
11480             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11481                      first.tidy, second.tidy,
11482                      first.matchWins, second.matchWins,
11483                      appData.matchGames - (first.matchWins + second.matchWins));
11484             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11485             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11486             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11487             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11488                 first.twoMachinesColor = "black\n";
11489                 second.twoMachinesColor = "white\n";
11490             } else {
11491                 first.twoMachinesColor = "white\n";
11492                 second.twoMachinesColor = "black\n";
11493             }
11494         }
11495     }
11496     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11497         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11498       ExitAnalyzeMode();
11499     gameMode = nextGameMode;
11500     ModeHighlight();
11501     endingGame = 0;  /* [HGM] crash */
11502     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11503         if(matchMode == TRUE) { // match through command line: exit with or without popup
11504             if(ranking) {
11505                 ToNrEvent(forwardMostMove);
11506                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11507                 else ExitEvent(0);
11508             } else DisplayFatalError(buf, 0, 0);
11509         } else { // match through menu; just stop, with or without popup
11510             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11511             ModeHighlight();
11512             if(ranking){
11513                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11514             } else DisplayNote(buf);
11515       }
11516       if(ranking) free(ranking);
11517     }
11518 }
11519
11520 /* Assumes program was just initialized (initString sent).
11521    Leaves program in force mode. */
11522 void
11523 FeedMovesToProgram (ChessProgramState *cps, int upto)
11524 {
11525     int i;
11526
11527     if (appData.debugMode)
11528       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11529               startedFromSetupPosition ? "position and " : "",
11530               backwardMostMove, upto, cps->which);
11531     if(currentlyInitializedVariant != gameInfo.variant) {
11532       char buf[MSG_SIZ];
11533         // [HGM] variantswitch: make engine aware of new variant
11534         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11535                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11536                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11537         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11538         SendToProgram(buf, cps);
11539         currentlyInitializedVariant = gameInfo.variant;
11540     }
11541     SendToProgram("force\n", cps);
11542     if (startedFromSetupPosition) {
11543         SendBoard(cps, backwardMostMove);
11544     if (appData.debugMode) {
11545         fprintf(debugFP, "feedMoves\n");
11546     }
11547     }
11548     for (i = backwardMostMove; i < upto; i++) {
11549         SendMoveToProgram(i, cps);
11550     }
11551 }
11552
11553
11554 int
11555 ResurrectChessProgram ()
11556 {
11557      /* The chess program may have exited.
11558         If so, restart it and feed it all the moves made so far. */
11559     static int doInit = 0;
11560
11561     if (appData.noChessProgram) return 1;
11562
11563     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11564         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11565         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11566         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11567     } else {
11568         if (first.pr != NoProc) return 1;
11569         StartChessProgram(&first);
11570     }
11571     InitChessProgram(&first, FALSE);
11572     FeedMovesToProgram(&first, currentMove);
11573
11574     if (!first.sendTime) {
11575         /* can't tell gnuchess what its clock should read,
11576            so we bow to its notion. */
11577         ResetClocks();
11578         timeRemaining[0][currentMove] = whiteTimeRemaining;
11579         timeRemaining[1][currentMove] = blackTimeRemaining;
11580     }
11581
11582     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11583                 appData.icsEngineAnalyze) && first.analysisSupport) {
11584       SendToProgram("analyze\n", &first);
11585       first.analyzing = TRUE;
11586     }
11587     return 1;
11588 }
11589
11590 /*
11591  * Button procedures
11592  */
11593 void
11594 Reset (int redraw, int init)
11595 {
11596     int i;
11597
11598     if (appData.debugMode) {
11599         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11600                 redraw, init, gameMode);
11601     }
11602     CleanupTail(); // [HGM] vari: delete any stored variations
11603     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11604     pausing = pauseExamInvalid = FALSE;
11605     startedFromSetupPosition = blackPlaysFirst = FALSE;
11606     firstMove = TRUE;
11607     whiteFlag = blackFlag = FALSE;
11608     userOfferedDraw = FALSE;
11609     hintRequested = bookRequested = FALSE;
11610     first.maybeThinking = FALSE;
11611     second.maybeThinking = FALSE;
11612     first.bookSuspend = FALSE; // [HGM] book
11613     second.bookSuspend = FALSE;
11614     thinkOutput[0] = NULLCHAR;
11615     lastHint[0] = NULLCHAR;
11616     ClearGameInfo(&gameInfo);
11617     gameInfo.variant = StringToVariant(appData.variant);
11618     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11619     ics_user_moved = ics_clock_paused = FALSE;
11620     ics_getting_history = H_FALSE;
11621     ics_gamenum = -1;
11622     white_holding[0] = black_holding[0] = NULLCHAR;
11623     ClearProgramStats();
11624     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11625
11626     ResetFrontEnd();
11627     ClearHighlights();
11628     flipView = appData.flipView;
11629     ClearPremoveHighlights();
11630     gotPremove = FALSE;
11631     alarmSounded = FALSE;
11632     killX = killY = -1; // [HGM] lion
11633
11634     GameEnds(EndOfFile, NULL, GE_PLAYER);
11635     if(appData.serverMovesName != NULL) {
11636         /* [HGM] prepare to make moves file for broadcasting */
11637         clock_t t = clock();
11638         if(serverMoves != NULL) fclose(serverMoves);
11639         serverMoves = fopen(appData.serverMovesName, "r");
11640         if(serverMoves != NULL) {
11641             fclose(serverMoves);
11642             /* delay 15 sec before overwriting, so all clients can see end */
11643             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11644         }
11645         serverMoves = fopen(appData.serverMovesName, "w");
11646     }
11647
11648     ExitAnalyzeMode();
11649     gameMode = BeginningOfGame;
11650     ModeHighlight();
11651     if(appData.icsActive) gameInfo.variant = VariantNormal;
11652     currentMove = forwardMostMove = backwardMostMove = 0;
11653     MarkTargetSquares(1);
11654     InitPosition(redraw);
11655     for (i = 0; i < MAX_MOVES; i++) {
11656         if (commentList[i] != NULL) {
11657             free(commentList[i]);
11658             commentList[i] = NULL;
11659         }
11660     }
11661     ResetClocks();
11662     timeRemaining[0][0] = whiteTimeRemaining;
11663     timeRemaining[1][0] = blackTimeRemaining;
11664
11665     if (first.pr == NoProc) {
11666         StartChessProgram(&first);
11667     }
11668     if (init) {
11669             InitChessProgram(&first, startedFromSetupPosition);
11670     }
11671     DisplayTitle("");
11672     DisplayMessage("", "");
11673     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11674     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11675     ClearMap();        // [HGM] exclude: invalidate map
11676 }
11677
11678 void
11679 AutoPlayGameLoop ()
11680 {
11681     for (;;) {
11682         if (!AutoPlayOneMove())
11683           return;
11684         if (matchMode || appData.timeDelay == 0)
11685           continue;
11686         if (appData.timeDelay < 0)
11687           return;
11688         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11689         break;
11690     }
11691 }
11692
11693 void
11694 AnalyzeNextGame()
11695 {
11696     ReloadGame(1); // next game
11697 }
11698
11699 int
11700 AutoPlayOneMove ()
11701 {
11702     int fromX, fromY, toX, toY;
11703
11704     if (appData.debugMode) {
11705       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11706     }
11707
11708     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11709       return FALSE;
11710
11711     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11712       pvInfoList[currentMove].depth = programStats.depth;
11713       pvInfoList[currentMove].score = programStats.score;
11714       pvInfoList[currentMove].time  = 0;
11715       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11716       else { // append analysis of final position as comment
11717         char buf[MSG_SIZ];
11718         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11719         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11720       }
11721       programStats.depth = 0;
11722     }
11723
11724     if (currentMove >= forwardMostMove) {
11725       if(gameMode == AnalyzeFile) {
11726           if(appData.loadGameIndex == -1) {
11727             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11728           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11729           } else {
11730           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11731         }
11732       }
11733 //      gameMode = EndOfGame;
11734 //      ModeHighlight();
11735
11736       /* [AS] Clear current move marker at the end of a game */
11737       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11738
11739       return FALSE;
11740     }
11741
11742     toX = moveList[currentMove][2] - AAA;
11743     toY = moveList[currentMove][3] - ONE;
11744
11745     if (moveList[currentMove][1] == '@') {
11746         if (appData.highlightLastMove) {
11747             SetHighlights(-1, -1, toX, toY);
11748         }
11749     } else {
11750         fromX = moveList[currentMove][0] - AAA;
11751         fromY = moveList[currentMove][1] - ONE;
11752
11753         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11754
11755         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11756
11757         if (appData.highlightLastMove) {
11758             SetHighlights(fromX, fromY, toX, toY);
11759         }
11760     }
11761     DisplayMove(currentMove);
11762     SendMoveToProgram(currentMove++, &first);
11763     DisplayBothClocks();
11764     DrawPosition(FALSE, boards[currentMove]);
11765     // [HGM] PV info: always display, routine tests if empty
11766     DisplayComment(currentMove - 1, commentList[currentMove]);
11767     return TRUE;
11768 }
11769
11770
11771 int
11772 LoadGameOneMove (ChessMove readAhead)
11773 {
11774     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11775     char promoChar = NULLCHAR;
11776     ChessMove moveType;
11777     char move[MSG_SIZ];
11778     char *p, *q;
11779
11780     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11781         gameMode != AnalyzeMode && gameMode != Training) {
11782         gameFileFP = NULL;
11783         return FALSE;
11784     }
11785
11786     yyboardindex = forwardMostMove;
11787     if (readAhead != EndOfFile) {
11788       moveType = readAhead;
11789     } else {
11790       if (gameFileFP == NULL)
11791           return FALSE;
11792       moveType = (ChessMove) Myylex();
11793     }
11794
11795     done = FALSE;
11796     switch (moveType) {
11797       case Comment:
11798         if (appData.debugMode)
11799           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11800         p = yy_text;
11801
11802         /* append the comment but don't display it */
11803         AppendComment(currentMove, p, FALSE);
11804         return TRUE;
11805
11806       case WhiteCapturesEnPassant:
11807       case BlackCapturesEnPassant:
11808       case WhitePromotion:
11809       case BlackPromotion:
11810       case WhiteNonPromotion:
11811       case BlackNonPromotion:
11812       case NormalMove:
11813       case FirstLeg:
11814       case WhiteKingSideCastle:
11815       case WhiteQueenSideCastle:
11816       case BlackKingSideCastle:
11817       case BlackQueenSideCastle:
11818       case WhiteKingSideCastleWild:
11819       case WhiteQueenSideCastleWild:
11820       case BlackKingSideCastleWild:
11821       case BlackQueenSideCastleWild:
11822       /* PUSH Fabien */
11823       case WhiteHSideCastleFR:
11824       case WhiteASideCastleFR:
11825       case BlackHSideCastleFR:
11826       case BlackASideCastleFR:
11827       /* POP Fabien */
11828         if (appData.debugMode)
11829           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11830         fromX = currentMoveString[0] - AAA;
11831         fromY = currentMoveString[1] - ONE;
11832         toX = currentMoveString[2] - AAA;
11833         toY = currentMoveString[3] - ONE;
11834         promoChar = currentMoveString[4];
11835         if(promoChar == ';') promoChar = NULLCHAR;
11836         break;
11837
11838       case WhiteDrop:
11839       case BlackDrop:
11840         if (appData.debugMode)
11841           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11842         fromX = moveType == WhiteDrop ?
11843           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11844         (int) CharToPiece(ToLower(currentMoveString[0]));
11845         fromY = DROP_RANK;
11846         toX = currentMoveString[2] - AAA;
11847         toY = currentMoveString[3] - ONE;
11848         break;
11849
11850       case WhiteWins:
11851       case BlackWins:
11852       case GameIsDrawn:
11853       case GameUnfinished:
11854         if (appData.debugMode)
11855           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11856         p = strchr(yy_text, '{');
11857         if (p == NULL) p = strchr(yy_text, '(');
11858         if (p == NULL) {
11859             p = yy_text;
11860             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11861         } else {
11862             q = strchr(p, *p == '{' ? '}' : ')');
11863             if (q != NULL) *q = NULLCHAR;
11864             p++;
11865         }
11866         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11867         GameEnds(moveType, p, GE_FILE);
11868         done = TRUE;
11869         if (cmailMsgLoaded) {
11870             ClearHighlights();
11871             flipView = WhiteOnMove(currentMove);
11872             if (moveType == GameUnfinished) flipView = !flipView;
11873             if (appData.debugMode)
11874               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11875         }
11876         break;
11877
11878       case EndOfFile:
11879         if (appData.debugMode)
11880           fprintf(debugFP, "Parser hit end of file\n");
11881         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11882           case MT_NONE:
11883           case MT_CHECK:
11884             break;
11885           case MT_CHECKMATE:
11886           case MT_STAINMATE:
11887             if (WhiteOnMove(currentMove)) {
11888                 GameEnds(BlackWins, "Black mates", GE_FILE);
11889             } else {
11890                 GameEnds(WhiteWins, "White mates", GE_FILE);
11891             }
11892             break;
11893           case MT_STALEMATE:
11894             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11895             break;
11896         }
11897         done = TRUE;
11898         break;
11899
11900       case MoveNumberOne:
11901         if (lastLoadGameStart == GNUChessGame) {
11902             /* GNUChessGames have numbers, but they aren't move numbers */
11903             if (appData.debugMode)
11904               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11905                       yy_text, (int) moveType);
11906             return LoadGameOneMove(EndOfFile); /* tail recursion */
11907         }
11908         /* else fall thru */
11909
11910       case XBoardGame:
11911       case GNUChessGame:
11912       case PGNTag:
11913         /* Reached start of next game in file */
11914         if (appData.debugMode)
11915           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11916         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11917           case MT_NONE:
11918           case MT_CHECK:
11919             break;
11920           case MT_CHECKMATE:
11921           case MT_STAINMATE:
11922             if (WhiteOnMove(currentMove)) {
11923                 GameEnds(BlackWins, "Black mates", GE_FILE);
11924             } else {
11925                 GameEnds(WhiteWins, "White mates", GE_FILE);
11926             }
11927             break;
11928           case MT_STALEMATE:
11929             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11930             break;
11931         }
11932         done = TRUE;
11933         break;
11934
11935       case PositionDiagram:     /* should not happen; ignore */
11936       case ElapsedTime:         /* ignore */
11937       case NAG:                 /* ignore */
11938         if (appData.debugMode)
11939           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11940                   yy_text, (int) moveType);
11941         return LoadGameOneMove(EndOfFile); /* tail recursion */
11942
11943       case IllegalMove:
11944         if (appData.testLegality) {
11945             if (appData.debugMode)
11946               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11947             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11948                     (forwardMostMove / 2) + 1,
11949                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11950             DisplayError(move, 0);
11951             done = TRUE;
11952         } else {
11953             if (appData.debugMode)
11954               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11955                       yy_text, currentMoveString);
11956             fromX = currentMoveString[0] - AAA;
11957             fromY = currentMoveString[1] - ONE;
11958             toX = currentMoveString[2] - AAA;
11959             toY = currentMoveString[3] - ONE;
11960             promoChar = currentMoveString[4];
11961         }
11962         break;
11963
11964       case AmbiguousMove:
11965         if (appData.debugMode)
11966           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11967         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11968                 (forwardMostMove / 2) + 1,
11969                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11970         DisplayError(move, 0);
11971         done = TRUE;
11972         break;
11973
11974       default:
11975       case ImpossibleMove:
11976         if (appData.debugMode)
11977           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11978         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11979                 (forwardMostMove / 2) + 1,
11980                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11981         DisplayError(move, 0);
11982         done = TRUE;
11983         break;
11984     }
11985
11986     if (done) {
11987         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11988             DrawPosition(FALSE, boards[currentMove]);
11989             DisplayBothClocks();
11990             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11991               DisplayComment(currentMove - 1, commentList[currentMove]);
11992         }
11993         (void) StopLoadGameTimer();
11994         gameFileFP = NULL;
11995         cmailOldMove = forwardMostMove;
11996         return FALSE;
11997     } else {
11998         /* currentMoveString is set as a side-effect of yylex */
11999
12000         thinkOutput[0] = NULLCHAR;
12001         MakeMove(fromX, fromY, toX, toY, promoChar);
12002         killX = killY = -1; // [HGM] lion: used up
12003         currentMove = forwardMostMove;
12004         return TRUE;
12005     }
12006 }
12007
12008 /* Load the nth game from the given file */
12009 int
12010 LoadGameFromFile (char *filename, int n, char *title, int useList)
12011 {
12012     FILE *f;
12013     char buf[MSG_SIZ];
12014
12015     if (strcmp(filename, "-") == 0) {
12016         f = stdin;
12017         title = "stdin";
12018     } else {
12019         f = fopen(filename, "rb");
12020         if (f == NULL) {
12021           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12022             DisplayError(buf, errno);
12023             return FALSE;
12024         }
12025     }
12026     if (fseek(f, 0, 0) == -1) {
12027         /* f is not seekable; probably a pipe */
12028         useList = FALSE;
12029     }
12030     if (useList && n == 0) {
12031         int error = GameListBuild(f);
12032         if (error) {
12033             DisplayError(_("Cannot build game list"), error);
12034         } else if (!ListEmpty(&gameList) &&
12035                    ((ListGame *) gameList.tailPred)->number > 1) {
12036             GameListPopUp(f, title);
12037             return TRUE;
12038         }
12039         GameListDestroy();
12040         n = 1;
12041     }
12042     if (n == 0) n = 1;
12043     return LoadGame(f, n, title, FALSE);
12044 }
12045
12046
12047 void
12048 MakeRegisteredMove ()
12049 {
12050     int fromX, fromY, toX, toY;
12051     char promoChar;
12052     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12053         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12054           case CMAIL_MOVE:
12055           case CMAIL_DRAW:
12056             if (appData.debugMode)
12057               fprintf(debugFP, "Restoring %s for game %d\n",
12058                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12059
12060             thinkOutput[0] = NULLCHAR;
12061             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12062             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12063             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12064             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12065             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12066             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12067             MakeMove(fromX, fromY, toX, toY, promoChar);
12068             ShowMove(fromX, fromY, toX, toY);
12069
12070             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12071               case MT_NONE:
12072               case MT_CHECK:
12073                 break;
12074
12075               case MT_CHECKMATE:
12076               case MT_STAINMATE:
12077                 if (WhiteOnMove(currentMove)) {
12078                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12079                 } else {
12080                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12081                 }
12082                 break;
12083
12084               case MT_STALEMATE:
12085                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12086                 break;
12087             }
12088
12089             break;
12090
12091           case CMAIL_RESIGN:
12092             if (WhiteOnMove(currentMove)) {
12093                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12094             } else {
12095                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12096             }
12097             break;
12098
12099           case CMAIL_ACCEPT:
12100             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12101             break;
12102
12103           default:
12104             break;
12105         }
12106     }
12107
12108     return;
12109 }
12110
12111 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12112 int
12113 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12114 {
12115     int retVal;
12116
12117     if (gameNumber > nCmailGames) {
12118         DisplayError(_("No more games in this message"), 0);
12119         return FALSE;
12120     }
12121     if (f == lastLoadGameFP) {
12122         int offset = gameNumber - lastLoadGameNumber;
12123         if (offset == 0) {
12124             cmailMsg[0] = NULLCHAR;
12125             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12126                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12127                 nCmailMovesRegistered--;
12128             }
12129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12130             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12131                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12132             }
12133         } else {
12134             if (! RegisterMove()) return FALSE;
12135         }
12136     }
12137
12138     retVal = LoadGame(f, gameNumber, title, useList);
12139
12140     /* Make move registered during previous look at this game, if any */
12141     MakeRegisteredMove();
12142
12143     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12144         commentList[currentMove]
12145           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12146         DisplayComment(currentMove - 1, commentList[currentMove]);
12147     }
12148
12149     return retVal;
12150 }
12151
12152 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12153 int
12154 ReloadGame (int offset)
12155 {
12156     int gameNumber = lastLoadGameNumber + offset;
12157     if (lastLoadGameFP == NULL) {
12158         DisplayError(_("No game has been loaded yet"), 0);
12159         return FALSE;
12160     }
12161     if (gameNumber <= 0) {
12162         DisplayError(_("Can't back up any further"), 0);
12163         return FALSE;
12164     }
12165     if (cmailMsgLoaded) {
12166         return CmailLoadGame(lastLoadGameFP, gameNumber,
12167                              lastLoadGameTitle, lastLoadGameUseList);
12168     } else {
12169         return LoadGame(lastLoadGameFP, gameNumber,
12170                         lastLoadGameTitle, lastLoadGameUseList);
12171     }
12172 }
12173
12174 int keys[EmptySquare+1];
12175
12176 int
12177 PositionMatches (Board b1, Board b2)
12178 {
12179     int r, f, sum=0;
12180     switch(appData.searchMode) {
12181         case 1: return CompareWithRights(b1, b2);
12182         case 2:
12183             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12184                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12185             }
12186             return TRUE;
12187         case 3:
12188             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12189               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12190                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12191             }
12192             return sum==0;
12193         case 4:
12194             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12195                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12196             }
12197             return sum==0;
12198     }
12199     return TRUE;
12200 }
12201
12202 #define Q_PROMO  4
12203 #define Q_EP     3
12204 #define Q_BCASTL 2
12205 #define Q_WCASTL 1
12206
12207 int pieceList[256], quickBoard[256];
12208 ChessSquare pieceType[256] = { EmptySquare };
12209 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12210 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12211 int soughtTotal, turn;
12212 Boolean epOK, flipSearch;
12213
12214 typedef struct {
12215     unsigned char piece, to;
12216 } Move;
12217
12218 #define DSIZE (250000)
12219
12220 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12221 Move *moveDatabase = initialSpace;
12222 unsigned int movePtr, dataSize = DSIZE;
12223
12224 int
12225 MakePieceList (Board board, int *counts)
12226 {
12227     int r, f, n=Q_PROMO, total=0;
12228     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12229     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12230         int sq = f + (r<<4);
12231         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12232             quickBoard[sq] = ++n;
12233             pieceList[n] = sq;
12234             pieceType[n] = board[r][f];
12235             counts[board[r][f]]++;
12236             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12237             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12238             total++;
12239         }
12240     }
12241     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12242     return total;
12243 }
12244
12245 void
12246 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12247 {
12248     int sq = fromX + (fromY<<4);
12249     int piece = quickBoard[sq], rook;
12250     quickBoard[sq] = 0;
12251     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12252     if(piece == pieceList[1] && fromY == toY) {
12253       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12254         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12255         moveDatabase[movePtr++].piece = Q_WCASTL;
12256         quickBoard[sq] = piece;
12257         piece = quickBoard[from]; quickBoard[from] = 0;
12258         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12259       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12260         quickBoard[sq] = 0; // remove Rook
12261         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12262         moveDatabase[movePtr++].piece = Q_WCASTL;
12263         quickBoard[sq] = pieceList[1]; // put King
12264         piece = rook;
12265         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12266       }
12267     } else
12268     if(piece == pieceList[2] && fromY == toY) {
12269       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12270         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12271         moveDatabase[movePtr++].piece = Q_BCASTL;
12272         quickBoard[sq] = piece;
12273         piece = quickBoard[from]; quickBoard[from] = 0;
12274         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12275       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12276         quickBoard[sq] = 0; // remove Rook
12277         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12278         moveDatabase[movePtr++].piece = Q_BCASTL;
12279         quickBoard[sq] = pieceList[2]; // put King
12280         piece = rook;
12281         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12282       }
12283     } else
12284     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12285         quickBoard[(fromY<<4)+toX] = 0;
12286         moveDatabase[movePtr].piece = Q_EP;
12287         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12288         moveDatabase[movePtr].to = sq;
12289     } else
12290     if(promoPiece != pieceType[piece]) {
12291         moveDatabase[movePtr++].piece = Q_PROMO;
12292         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12293     }
12294     moveDatabase[movePtr].piece = piece;
12295     quickBoard[sq] = piece;
12296     movePtr++;
12297 }
12298
12299 int
12300 PackGame (Board board)
12301 {
12302     Move *newSpace = NULL;
12303     moveDatabase[movePtr].piece = 0; // terminate previous game
12304     if(movePtr > dataSize) {
12305         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12306         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12307         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12308         if(newSpace) {
12309             int i;
12310             Move *p = moveDatabase, *q = newSpace;
12311             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12312             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12313             moveDatabase = newSpace;
12314         } else { // calloc failed, we must be out of memory. Too bad...
12315             dataSize = 0; // prevent calloc events for all subsequent games
12316             return 0;     // and signal this one isn't cached
12317         }
12318     }
12319     movePtr++;
12320     MakePieceList(board, counts);
12321     return movePtr;
12322 }
12323
12324 int
12325 QuickCompare (Board board, int *minCounts, int *maxCounts)
12326 {   // compare according to search mode
12327     int r, f;
12328     switch(appData.searchMode)
12329     {
12330       case 1: // exact position match
12331         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12332         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12333             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12334         }
12335         break;
12336       case 2: // can have extra material on empty squares
12337         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12338             if(board[r][f] == EmptySquare) continue;
12339             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12340         }
12341         break;
12342       case 3: // material with exact Pawn structure
12343         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12344             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12345             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12346         } // fall through to material comparison
12347       case 4: // exact material
12348         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12349         break;
12350       case 6: // material range with given imbalance
12351         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12352         // fall through to range comparison
12353       case 5: // material range
12354         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12355     }
12356     return TRUE;
12357 }
12358
12359 int
12360 QuickScan (Board board, Move *move)
12361 {   // reconstruct game,and compare all positions in it
12362     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12363     do {
12364         int piece = move->piece;
12365         int to = move->to, from = pieceList[piece];
12366         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12367           if(!piece) return -1;
12368           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12369             piece = (++move)->piece;
12370             from = pieceList[piece];
12371             counts[pieceType[piece]]--;
12372             pieceType[piece] = (ChessSquare) move->to;
12373             counts[move->to]++;
12374           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12375             counts[pieceType[quickBoard[to]]]--;
12376             quickBoard[to] = 0; total--;
12377             move++;
12378             continue;
12379           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12380             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12381             from  = pieceList[piece]; // so this must be King
12382             quickBoard[from] = 0;
12383             pieceList[piece] = to;
12384             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12385             quickBoard[from] = 0; // rook
12386             quickBoard[to] = piece;
12387             to = move->to; piece = move->piece;
12388             goto aftercastle;
12389           }
12390         }
12391         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12392         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12393         quickBoard[from] = 0;
12394       aftercastle:
12395         quickBoard[to] = piece;
12396         pieceList[piece] = to;
12397         cnt++; turn ^= 3;
12398         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12399            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12400            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12401                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12402           ) {
12403             static int lastCounts[EmptySquare+1];
12404             int i;
12405             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12406             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12407         } else stretch = 0;
12408         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12409         move++;
12410     } while(1);
12411 }
12412
12413 void
12414 InitSearch ()
12415 {
12416     int r, f;
12417     flipSearch = FALSE;
12418     CopyBoard(soughtBoard, boards[currentMove]);
12419     soughtTotal = MakePieceList(soughtBoard, maxSought);
12420     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12421     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12422     CopyBoard(reverseBoard, boards[currentMove]);
12423     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12424         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12425         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12426         reverseBoard[r][f] = piece;
12427     }
12428     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12429     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12430     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12431                  || (boards[currentMove][CASTLING][2] == NoRights ||
12432                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12433                  && (boards[currentMove][CASTLING][5] == NoRights ||
12434                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12435       ) {
12436         flipSearch = TRUE;
12437         CopyBoard(flipBoard, soughtBoard);
12438         CopyBoard(rotateBoard, reverseBoard);
12439         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12440             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12441             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12442         }
12443     }
12444     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12445     if(appData.searchMode >= 5) {
12446         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12447         MakePieceList(soughtBoard, minSought);
12448         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12449     }
12450     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12451         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12452 }
12453
12454 GameInfo dummyInfo;
12455 static int creatingBook;
12456
12457 int
12458 GameContainsPosition (FILE *f, ListGame *lg)
12459 {
12460     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12461     int fromX, fromY, toX, toY;
12462     char promoChar;
12463     static int initDone=FALSE;
12464
12465     // weed out games based on numerical tag comparison
12466     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12467     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12468     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12469     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12470     if(!initDone) {
12471         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12472         initDone = TRUE;
12473     }
12474     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12475     else CopyBoard(boards[scratch], initialPosition); // default start position
12476     if(lg->moves) {
12477         turn = btm + 1;
12478         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12479         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12480     }
12481     if(btm) plyNr++;
12482     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12483     fseek(f, lg->offset, 0);
12484     yynewfile(f);
12485     while(1) {
12486         yyboardindex = scratch;
12487         quickFlag = plyNr+1;
12488         next = Myylex();
12489         quickFlag = 0;
12490         switch(next) {
12491             case PGNTag:
12492                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12493             default:
12494                 continue;
12495
12496             case XBoardGame:
12497             case GNUChessGame:
12498                 if(plyNr) return -1; // after we have seen moves, this is for new game
12499               continue;
12500
12501             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12502             case ImpossibleMove:
12503             case WhiteWins: // game ends here with these four
12504             case BlackWins:
12505             case GameIsDrawn:
12506             case GameUnfinished:
12507                 return -1;
12508
12509             case IllegalMove:
12510                 if(appData.testLegality) return -1;
12511             case WhiteCapturesEnPassant:
12512             case BlackCapturesEnPassant:
12513             case WhitePromotion:
12514             case BlackPromotion:
12515             case WhiteNonPromotion:
12516             case BlackNonPromotion:
12517             case NormalMove:
12518             case FirstLeg:
12519             case WhiteKingSideCastle:
12520             case WhiteQueenSideCastle:
12521             case BlackKingSideCastle:
12522             case BlackQueenSideCastle:
12523             case WhiteKingSideCastleWild:
12524             case WhiteQueenSideCastleWild:
12525             case BlackKingSideCastleWild:
12526             case BlackQueenSideCastleWild:
12527             case WhiteHSideCastleFR:
12528             case WhiteASideCastleFR:
12529             case BlackHSideCastleFR:
12530             case BlackASideCastleFR:
12531                 fromX = currentMoveString[0] - AAA;
12532                 fromY = currentMoveString[1] - ONE;
12533                 toX = currentMoveString[2] - AAA;
12534                 toY = currentMoveString[3] - ONE;
12535                 promoChar = currentMoveString[4];
12536                 break;
12537             case WhiteDrop:
12538             case BlackDrop:
12539                 fromX = next == WhiteDrop ?
12540                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12541                   (int) CharToPiece(ToLower(currentMoveString[0]));
12542                 fromY = DROP_RANK;
12543                 toX = currentMoveString[2] - AAA;
12544                 toY = currentMoveString[3] - ONE;
12545                 promoChar = 0;
12546                 break;
12547         }
12548         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12549         plyNr++;
12550         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12551         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12552         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12553         if(appData.findMirror) {
12554             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12555             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12556         }
12557     }
12558 }
12559
12560 /* Load the nth game from open file f */
12561 int
12562 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12563 {
12564     ChessMove cm;
12565     char buf[MSG_SIZ];
12566     int gn = gameNumber;
12567     ListGame *lg = NULL;
12568     int numPGNTags = 0;
12569     int err, pos = -1;
12570     GameMode oldGameMode;
12571     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12572
12573     if (appData.debugMode)
12574         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12575
12576     if (gameMode == Training )
12577         SetTrainingModeOff();
12578
12579     oldGameMode = gameMode;
12580     if (gameMode != BeginningOfGame) {
12581       Reset(FALSE, TRUE);
12582     }
12583     killX = killY = -1; // [HGM] lion: in case we did not Reset
12584
12585     gameFileFP = f;
12586     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12587         fclose(lastLoadGameFP);
12588     }
12589
12590     if (useList) {
12591         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12592
12593         if (lg) {
12594             fseek(f, lg->offset, 0);
12595             GameListHighlight(gameNumber);
12596             pos = lg->position;
12597             gn = 1;
12598         }
12599         else {
12600             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12601               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12602             else
12603             DisplayError(_("Game number out of range"), 0);
12604             return FALSE;
12605         }
12606     } else {
12607         GameListDestroy();
12608         if (fseek(f, 0, 0) == -1) {
12609             if (f == lastLoadGameFP ?
12610                 gameNumber == lastLoadGameNumber + 1 :
12611                 gameNumber == 1) {
12612                 gn = 1;
12613             } else {
12614                 DisplayError(_("Can't seek on game file"), 0);
12615                 return FALSE;
12616             }
12617         }
12618     }
12619     lastLoadGameFP = f;
12620     lastLoadGameNumber = gameNumber;
12621     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12622     lastLoadGameUseList = useList;
12623
12624     yynewfile(f);
12625
12626     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12627       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12628                 lg->gameInfo.black);
12629             DisplayTitle(buf);
12630     } else if (*title != NULLCHAR) {
12631         if (gameNumber > 1) {
12632           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12633             DisplayTitle(buf);
12634         } else {
12635             DisplayTitle(title);
12636         }
12637     }
12638
12639     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12640         gameMode = PlayFromGameFile;
12641         ModeHighlight();
12642     }
12643
12644     currentMove = forwardMostMove = backwardMostMove = 0;
12645     CopyBoard(boards[0], initialPosition);
12646     StopClocks();
12647
12648     /*
12649      * Skip the first gn-1 games in the file.
12650      * Also skip over anything that precedes an identifiable
12651      * start of game marker, to avoid being confused by
12652      * garbage at the start of the file.  Currently
12653      * recognized start of game markers are the move number "1",
12654      * the pattern "gnuchess .* game", the pattern
12655      * "^[#;%] [^ ]* game file", and a PGN tag block.
12656      * A game that starts with one of the latter two patterns
12657      * will also have a move number 1, possibly
12658      * following a position diagram.
12659      * 5-4-02: Let's try being more lenient and allowing a game to
12660      * start with an unnumbered move.  Does that break anything?
12661      */
12662     cm = lastLoadGameStart = EndOfFile;
12663     while (gn > 0) {
12664         yyboardindex = forwardMostMove;
12665         cm = (ChessMove) Myylex();
12666         switch (cm) {
12667           case EndOfFile:
12668             if (cmailMsgLoaded) {
12669                 nCmailGames = CMAIL_MAX_GAMES - gn;
12670             } else {
12671                 Reset(TRUE, TRUE);
12672                 DisplayError(_("Game not found in file"), 0);
12673             }
12674             return FALSE;
12675
12676           case GNUChessGame:
12677           case XBoardGame:
12678             gn--;
12679             lastLoadGameStart = cm;
12680             break;
12681
12682           case MoveNumberOne:
12683             switch (lastLoadGameStart) {
12684               case GNUChessGame:
12685               case XBoardGame:
12686               case PGNTag:
12687                 break;
12688               case MoveNumberOne:
12689               case EndOfFile:
12690                 gn--;           /* count this game */
12691                 lastLoadGameStart = cm;
12692                 break;
12693               default:
12694                 /* impossible */
12695                 break;
12696             }
12697             break;
12698
12699           case PGNTag:
12700             switch (lastLoadGameStart) {
12701               case GNUChessGame:
12702               case PGNTag:
12703               case MoveNumberOne:
12704               case EndOfFile:
12705                 gn--;           /* count this game */
12706                 lastLoadGameStart = cm;
12707                 break;
12708               case XBoardGame:
12709                 lastLoadGameStart = cm; /* game counted already */
12710                 break;
12711               default:
12712                 /* impossible */
12713                 break;
12714             }
12715             if (gn > 0) {
12716                 do {
12717                     yyboardindex = forwardMostMove;
12718                     cm = (ChessMove) Myylex();
12719                 } while (cm == PGNTag || cm == Comment);
12720             }
12721             break;
12722
12723           case WhiteWins:
12724           case BlackWins:
12725           case GameIsDrawn:
12726             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12727                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12728                     != CMAIL_OLD_RESULT) {
12729                     nCmailResults ++ ;
12730                     cmailResult[  CMAIL_MAX_GAMES
12731                                 - gn - 1] = CMAIL_OLD_RESULT;
12732                 }
12733             }
12734             break;
12735
12736           case NormalMove:
12737           case FirstLeg:
12738             /* Only a NormalMove can be at the start of a game
12739              * without a position diagram. */
12740             if (lastLoadGameStart == EndOfFile ) {
12741               gn--;
12742               lastLoadGameStart = MoveNumberOne;
12743             }
12744             break;
12745
12746           default:
12747             break;
12748         }
12749     }
12750
12751     if (appData.debugMode)
12752       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12753
12754     if (cm == XBoardGame) {
12755         /* Skip any header junk before position diagram and/or move 1 */
12756         for (;;) {
12757             yyboardindex = forwardMostMove;
12758             cm = (ChessMove) Myylex();
12759
12760             if (cm == EndOfFile ||
12761                 cm == GNUChessGame || cm == XBoardGame) {
12762                 /* Empty game; pretend end-of-file and handle later */
12763                 cm = EndOfFile;
12764                 break;
12765             }
12766
12767             if (cm == MoveNumberOne || cm == PositionDiagram ||
12768                 cm == PGNTag || cm == Comment)
12769               break;
12770         }
12771     } else if (cm == GNUChessGame) {
12772         if (gameInfo.event != NULL) {
12773             free(gameInfo.event);
12774         }
12775         gameInfo.event = StrSave(yy_text);
12776     }
12777
12778     startedFromSetupPosition = FALSE;
12779     while (cm == PGNTag) {
12780         if (appData.debugMode)
12781           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12782         err = ParsePGNTag(yy_text, &gameInfo);
12783         if (!err) numPGNTags++;
12784
12785         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12786         if(gameInfo.variant != oldVariant) {
12787             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12788             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12789             InitPosition(TRUE);
12790             oldVariant = gameInfo.variant;
12791             if (appData.debugMode)
12792               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12793         }
12794
12795
12796         if (gameInfo.fen != NULL) {
12797           Board initial_position;
12798           startedFromSetupPosition = TRUE;
12799           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12800             Reset(TRUE, TRUE);
12801             DisplayError(_("Bad FEN position in file"), 0);
12802             return FALSE;
12803           }
12804           CopyBoard(boards[0], initial_position);
12805           if (blackPlaysFirst) {
12806             currentMove = forwardMostMove = backwardMostMove = 1;
12807             CopyBoard(boards[1], initial_position);
12808             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12809             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12810             timeRemaining[0][1] = whiteTimeRemaining;
12811             timeRemaining[1][1] = blackTimeRemaining;
12812             if (commentList[0] != NULL) {
12813               commentList[1] = commentList[0];
12814               commentList[0] = NULL;
12815             }
12816           } else {
12817             currentMove = forwardMostMove = backwardMostMove = 0;
12818           }
12819           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12820           {   int i;
12821               initialRulePlies = FENrulePlies;
12822               for( i=0; i< nrCastlingRights; i++ )
12823                   initialRights[i] = initial_position[CASTLING][i];
12824           }
12825           yyboardindex = forwardMostMove;
12826           free(gameInfo.fen);
12827           gameInfo.fen = NULL;
12828         }
12829
12830         yyboardindex = forwardMostMove;
12831         cm = (ChessMove) Myylex();
12832
12833         /* Handle comments interspersed among the tags */
12834         while (cm == Comment) {
12835             char *p;
12836             if (appData.debugMode)
12837               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12838             p = yy_text;
12839             AppendComment(currentMove, p, FALSE);
12840             yyboardindex = forwardMostMove;
12841             cm = (ChessMove) Myylex();
12842         }
12843     }
12844
12845     /* don't rely on existence of Event tag since if game was
12846      * pasted from clipboard the Event tag may not exist
12847      */
12848     if (numPGNTags > 0){
12849         char *tags;
12850         if (gameInfo.variant == VariantNormal) {
12851           VariantClass v = StringToVariant(gameInfo.event);
12852           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12853           if(v < VariantShogi) gameInfo.variant = v;
12854         }
12855         if (!matchMode) {
12856           if( appData.autoDisplayTags ) {
12857             tags = PGNTags(&gameInfo);
12858             TagsPopUp(tags, CmailMsg());
12859             free(tags);
12860           }
12861         }
12862     } else {
12863         /* Make something up, but don't display it now */
12864         SetGameInfo();
12865         TagsPopDown();
12866     }
12867
12868     if (cm == PositionDiagram) {
12869         int i, j;
12870         char *p;
12871         Board initial_position;
12872
12873         if (appData.debugMode)
12874           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12875
12876         if (!startedFromSetupPosition) {
12877             p = yy_text;
12878             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12879               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12880                 switch (*p) {
12881                   case '{':
12882                   case '[':
12883                   case '-':
12884                   case ' ':
12885                   case '\t':
12886                   case '\n':
12887                   case '\r':
12888                     break;
12889                   default:
12890                     initial_position[i][j++] = CharToPiece(*p);
12891                     break;
12892                 }
12893             while (*p == ' ' || *p == '\t' ||
12894                    *p == '\n' || *p == '\r') p++;
12895
12896             if (strncmp(p, "black", strlen("black"))==0)
12897               blackPlaysFirst = TRUE;
12898             else
12899               blackPlaysFirst = FALSE;
12900             startedFromSetupPosition = TRUE;
12901
12902             CopyBoard(boards[0], initial_position);
12903             if (blackPlaysFirst) {
12904                 currentMove = forwardMostMove = backwardMostMove = 1;
12905                 CopyBoard(boards[1], initial_position);
12906                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12907                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12908                 timeRemaining[0][1] = whiteTimeRemaining;
12909                 timeRemaining[1][1] = blackTimeRemaining;
12910                 if (commentList[0] != NULL) {
12911                     commentList[1] = commentList[0];
12912                     commentList[0] = NULL;
12913                 }
12914             } else {
12915                 currentMove = forwardMostMove = backwardMostMove = 0;
12916             }
12917         }
12918         yyboardindex = forwardMostMove;
12919         cm = (ChessMove) Myylex();
12920     }
12921
12922   if(!creatingBook) {
12923     if (first.pr == NoProc) {
12924         StartChessProgram(&first);
12925     }
12926     InitChessProgram(&first, FALSE);
12927     SendToProgram("force\n", &first);
12928     if (startedFromSetupPosition) {
12929         SendBoard(&first, forwardMostMove);
12930     if (appData.debugMode) {
12931         fprintf(debugFP, "Load Game\n");
12932     }
12933         DisplayBothClocks();
12934     }
12935   }
12936
12937     /* [HGM] server: flag to write setup moves in broadcast file as one */
12938     loadFlag = appData.suppressLoadMoves;
12939
12940     while (cm == Comment) {
12941         char *p;
12942         if (appData.debugMode)
12943           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12944         p = yy_text;
12945         AppendComment(currentMove, p, FALSE);
12946         yyboardindex = forwardMostMove;
12947         cm = (ChessMove) Myylex();
12948     }
12949
12950     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12951         cm == WhiteWins || cm == BlackWins ||
12952         cm == GameIsDrawn || cm == GameUnfinished) {
12953         DisplayMessage("", _("No moves in game"));
12954         if (cmailMsgLoaded) {
12955             if (appData.debugMode)
12956               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12957             ClearHighlights();
12958             flipView = FALSE;
12959         }
12960         DrawPosition(FALSE, boards[currentMove]);
12961         DisplayBothClocks();
12962         gameMode = EditGame;
12963         ModeHighlight();
12964         gameFileFP = NULL;
12965         cmailOldMove = 0;
12966         return TRUE;
12967     }
12968
12969     // [HGM] PV info: routine tests if comment empty
12970     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12971         DisplayComment(currentMove - 1, commentList[currentMove]);
12972     }
12973     if (!matchMode && appData.timeDelay != 0)
12974       DrawPosition(FALSE, boards[currentMove]);
12975
12976     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12977       programStats.ok_to_send = 1;
12978     }
12979
12980     /* if the first token after the PGN tags is a move
12981      * and not move number 1, retrieve it from the parser
12982      */
12983     if (cm != MoveNumberOne)
12984         LoadGameOneMove(cm);
12985
12986     /* load the remaining moves from the file */
12987     while (LoadGameOneMove(EndOfFile)) {
12988       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12989       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12990     }
12991
12992     /* rewind to the start of the game */
12993     currentMove = backwardMostMove;
12994
12995     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12996
12997     if (oldGameMode == AnalyzeFile) {
12998       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12999       AnalyzeFileEvent();
13000     } else
13001     if (oldGameMode == AnalyzeMode) {
13002       AnalyzeFileEvent();
13003     }
13004
13005     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13006         long int w, b; // [HGM] adjourn: restore saved clock times
13007         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13008         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13009             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13010             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13011         }
13012     }
13013
13014     if(creatingBook) return TRUE;
13015     if (!matchMode && pos > 0) {
13016         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13017     } else
13018     if (matchMode || appData.timeDelay == 0) {
13019       ToEndEvent();
13020     } else if (appData.timeDelay > 0) {
13021       AutoPlayGameLoop();
13022     }
13023
13024     if (appData.debugMode)
13025         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13026
13027     loadFlag = 0; /* [HGM] true game starts */
13028     return TRUE;
13029 }
13030
13031 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13032 int
13033 ReloadPosition (int offset)
13034 {
13035     int positionNumber = lastLoadPositionNumber + offset;
13036     if (lastLoadPositionFP == NULL) {
13037         DisplayError(_("No position has been loaded yet"), 0);
13038         return FALSE;
13039     }
13040     if (positionNumber <= 0) {
13041         DisplayError(_("Can't back up any further"), 0);
13042         return FALSE;
13043     }
13044     return LoadPosition(lastLoadPositionFP, positionNumber,
13045                         lastLoadPositionTitle);
13046 }
13047
13048 /* Load the nth position from the given file */
13049 int
13050 LoadPositionFromFile (char *filename, int n, char *title)
13051 {
13052     FILE *f;
13053     char buf[MSG_SIZ];
13054
13055     if (strcmp(filename, "-") == 0) {
13056         return LoadPosition(stdin, n, "stdin");
13057     } else {
13058         f = fopen(filename, "rb");
13059         if (f == NULL) {
13060             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13061             DisplayError(buf, errno);
13062             return FALSE;
13063         } else {
13064             return LoadPosition(f, n, title);
13065         }
13066     }
13067 }
13068
13069 /* Load the nth position from the given open file, and close it */
13070 int
13071 LoadPosition (FILE *f, int positionNumber, char *title)
13072 {
13073     char *p, line[MSG_SIZ];
13074     Board initial_position;
13075     int i, j, fenMode, pn;
13076
13077     if (gameMode == Training )
13078         SetTrainingModeOff();
13079
13080     if (gameMode != BeginningOfGame) {
13081         Reset(FALSE, TRUE);
13082     }
13083     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13084         fclose(lastLoadPositionFP);
13085     }
13086     if (positionNumber == 0) positionNumber = 1;
13087     lastLoadPositionFP = f;
13088     lastLoadPositionNumber = positionNumber;
13089     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13090     if (first.pr == NoProc && !appData.noChessProgram) {
13091       StartChessProgram(&first);
13092       InitChessProgram(&first, FALSE);
13093     }
13094     pn = positionNumber;
13095     if (positionNumber < 0) {
13096         /* Negative position number means to seek to that byte offset */
13097         if (fseek(f, -positionNumber, 0) == -1) {
13098             DisplayError(_("Can't seek on position file"), 0);
13099             return FALSE;
13100         };
13101         pn = 1;
13102     } else {
13103         if (fseek(f, 0, 0) == -1) {
13104             if (f == lastLoadPositionFP ?
13105                 positionNumber == lastLoadPositionNumber + 1 :
13106                 positionNumber == 1) {
13107                 pn = 1;
13108             } else {
13109                 DisplayError(_("Can't seek on position file"), 0);
13110                 return FALSE;
13111             }
13112         }
13113     }
13114     /* See if this file is FEN or old-style xboard */
13115     if (fgets(line, MSG_SIZ, f) == NULL) {
13116         DisplayError(_("Position not found in file"), 0);
13117         return FALSE;
13118     }
13119     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13120     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13121
13122     if (pn >= 2) {
13123         if (fenMode || line[0] == '#') pn--;
13124         while (pn > 0) {
13125             /* skip positions before number pn */
13126             if (fgets(line, MSG_SIZ, f) == NULL) {
13127                 Reset(TRUE, TRUE);
13128                 DisplayError(_("Position not found in file"), 0);
13129                 return FALSE;
13130             }
13131             if (fenMode || line[0] == '#') pn--;
13132         }
13133     }
13134
13135     if (fenMode) {
13136         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13137             DisplayError(_("Bad FEN position in file"), 0);
13138             return FALSE;
13139         }
13140     } else {
13141         (void) fgets(line, MSG_SIZ, f);
13142         (void) fgets(line, MSG_SIZ, f);
13143
13144         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13145             (void) fgets(line, MSG_SIZ, f);
13146             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13147                 if (*p == ' ')
13148                   continue;
13149                 initial_position[i][j++] = CharToPiece(*p);
13150             }
13151         }
13152
13153         blackPlaysFirst = FALSE;
13154         if (!feof(f)) {
13155             (void) fgets(line, MSG_SIZ, f);
13156             if (strncmp(line, "black", strlen("black"))==0)
13157               blackPlaysFirst = TRUE;
13158         }
13159     }
13160     startedFromSetupPosition = TRUE;
13161
13162     CopyBoard(boards[0], initial_position);
13163     if (blackPlaysFirst) {
13164         currentMove = forwardMostMove = backwardMostMove = 1;
13165         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13166         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13167         CopyBoard(boards[1], initial_position);
13168         DisplayMessage("", _("Black to play"));
13169     } else {
13170         currentMove = forwardMostMove = backwardMostMove = 0;
13171         DisplayMessage("", _("White to play"));
13172     }
13173     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13174     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13175         SendToProgram("force\n", &first);
13176         SendBoard(&first, forwardMostMove);
13177     }
13178     if (appData.debugMode) {
13179 int i, j;
13180   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13181   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13182         fprintf(debugFP, "Load Position\n");
13183     }
13184
13185     if (positionNumber > 1) {
13186       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13187         DisplayTitle(line);
13188     } else {
13189         DisplayTitle(title);
13190     }
13191     gameMode = EditGame;
13192     ModeHighlight();
13193     ResetClocks();
13194     timeRemaining[0][1] = whiteTimeRemaining;
13195     timeRemaining[1][1] = blackTimeRemaining;
13196     DrawPosition(FALSE, boards[currentMove]);
13197
13198     return TRUE;
13199 }
13200
13201
13202 void
13203 CopyPlayerNameIntoFileName (char **dest, char *src)
13204 {
13205     while (*src != NULLCHAR && *src != ',') {
13206         if (*src == ' ') {
13207             *(*dest)++ = '_';
13208             src++;
13209         } else {
13210             *(*dest)++ = *src++;
13211         }
13212     }
13213 }
13214
13215 char *
13216 DefaultFileName (char *ext)
13217 {
13218     static char def[MSG_SIZ];
13219     char *p;
13220
13221     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13222         p = def;
13223         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13224         *p++ = '-';
13225         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13226         *p++ = '.';
13227         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13228     } else {
13229         def[0] = NULLCHAR;
13230     }
13231     return def;
13232 }
13233
13234 /* Save the current game to the given file */
13235 int
13236 SaveGameToFile (char *filename, int append)
13237 {
13238     FILE *f;
13239     char buf[MSG_SIZ];
13240     int result, i, t,tot=0;
13241
13242     if (strcmp(filename, "-") == 0) {
13243         return SaveGame(stdout, 0, NULL);
13244     } else {
13245         for(i=0; i<10; i++) { // upto 10 tries
13246              f = fopen(filename, append ? "a" : "w");
13247              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13248              if(f || errno != 13) break;
13249              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13250              tot += t;
13251         }
13252         if (f == NULL) {
13253             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13254             DisplayError(buf, errno);
13255             return FALSE;
13256         } else {
13257             safeStrCpy(buf, lastMsg, MSG_SIZ);
13258             DisplayMessage(_("Waiting for access to save file"), "");
13259             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13260             DisplayMessage(_("Saving game"), "");
13261             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13262             result = SaveGame(f, 0, NULL);
13263             DisplayMessage(buf, "");
13264             return result;
13265         }
13266     }
13267 }
13268
13269 char *
13270 SavePart (char *str)
13271 {
13272     static char buf[MSG_SIZ];
13273     char *p;
13274
13275     p = strchr(str, ' ');
13276     if (p == NULL) return str;
13277     strncpy(buf, str, p - str);
13278     buf[p - str] = NULLCHAR;
13279     return buf;
13280 }
13281
13282 #define PGN_MAX_LINE 75
13283
13284 #define PGN_SIDE_WHITE  0
13285 #define PGN_SIDE_BLACK  1
13286
13287 static int
13288 FindFirstMoveOutOfBook (int side)
13289 {
13290     int result = -1;
13291
13292     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13293         int index = backwardMostMove;
13294         int has_book_hit = 0;
13295
13296         if( (index % 2) != side ) {
13297             index++;
13298         }
13299
13300         while( index < forwardMostMove ) {
13301             /* Check to see if engine is in book */
13302             int depth = pvInfoList[index].depth;
13303             int score = pvInfoList[index].score;
13304             int in_book = 0;
13305
13306             if( depth <= 2 ) {
13307                 in_book = 1;
13308             }
13309             else if( score == 0 && depth == 63 ) {
13310                 in_book = 1; /* Zappa */
13311             }
13312             else if( score == 2 && depth == 99 ) {
13313                 in_book = 1; /* Abrok */
13314             }
13315
13316             has_book_hit += in_book;
13317
13318             if( ! in_book ) {
13319                 result = index;
13320
13321                 break;
13322             }
13323
13324             index += 2;
13325         }
13326     }
13327
13328     return result;
13329 }
13330
13331 void
13332 GetOutOfBookInfo (char * buf)
13333 {
13334     int oob[2];
13335     int i;
13336     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13337
13338     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13339     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13340
13341     *buf = '\0';
13342
13343     if( oob[0] >= 0 || oob[1] >= 0 ) {
13344         for( i=0; i<2; i++ ) {
13345             int idx = oob[i];
13346
13347             if( idx >= 0 ) {
13348                 if( i > 0 && oob[0] >= 0 ) {
13349                     strcat( buf, "   " );
13350                 }
13351
13352                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13353                 sprintf( buf+strlen(buf), "%s%.2f",
13354                     pvInfoList[idx].score >= 0 ? "+" : "",
13355                     pvInfoList[idx].score / 100.0 );
13356             }
13357         }
13358     }
13359 }
13360
13361 /* Save game in PGN style and close the file */
13362 int
13363 SaveGamePGN (FILE *f)
13364 {
13365     int i, offset, linelen, newblock;
13366 //    char *movetext;
13367     char numtext[32];
13368     int movelen, numlen, blank;
13369     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13370
13371     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13372
13373     PrintPGNTags(f, &gameInfo);
13374
13375     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13376
13377     if (backwardMostMove > 0 || startedFromSetupPosition) {
13378         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13379         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13380         fprintf(f, "\n{--------------\n");
13381         PrintPosition(f, backwardMostMove);
13382         fprintf(f, "--------------}\n");
13383         free(fen);
13384     }
13385     else {
13386         /* [AS] Out of book annotation */
13387         if( appData.saveOutOfBookInfo ) {
13388             char buf[64];
13389
13390             GetOutOfBookInfo( buf );
13391
13392             if( buf[0] != '\0' ) {
13393                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13394             }
13395         }
13396
13397         fprintf(f, "\n");
13398     }
13399
13400     i = backwardMostMove;
13401     linelen = 0;
13402     newblock = TRUE;
13403
13404     while (i < forwardMostMove) {
13405         /* Print comments preceding this move */
13406         if (commentList[i] != NULL) {
13407             if (linelen > 0) fprintf(f, "\n");
13408             fprintf(f, "%s", commentList[i]);
13409             linelen = 0;
13410             newblock = TRUE;
13411         }
13412
13413         /* Format move number */
13414         if ((i % 2) == 0)
13415           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13416         else
13417           if (newblock)
13418             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13419           else
13420             numtext[0] = NULLCHAR;
13421
13422         numlen = strlen(numtext);
13423         newblock = FALSE;
13424
13425         /* Print move number */
13426         blank = linelen > 0 && numlen > 0;
13427         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13428             fprintf(f, "\n");
13429             linelen = 0;
13430             blank = 0;
13431         }
13432         if (blank) {
13433             fprintf(f, " ");
13434             linelen++;
13435         }
13436         fprintf(f, "%s", numtext);
13437         linelen += numlen;
13438
13439         /* Get move */
13440         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13441         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13442
13443         /* Print move */
13444         blank = linelen > 0 && movelen > 0;
13445         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13446             fprintf(f, "\n");
13447             linelen = 0;
13448             blank = 0;
13449         }
13450         if (blank) {
13451             fprintf(f, " ");
13452             linelen++;
13453         }
13454         fprintf(f, "%s", move_buffer);
13455         linelen += movelen;
13456
13457         /* [AS] Add PV info if present */
13458         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13459             /* [HGM] add time */
13460             char buf[MSG_SIZ]; int seconds;
13461
13462             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13463
13464             if( seconds <= 0)
13465               buf[0] = 0;
13466             else
13467               if( seconds < 30 )
13468                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13469               else
13470                 {
13471                   seconds = (seconds + 4)/10; // round to full seconds
13472                   if( seconds < 60 )
13473                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13474                   else
13475                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13476                 }
13477
13478             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13479                       pvInfoList[i].score >= 0 ? "+" : "",
13480                       pvInfoList[i].score / 100.0,
13481                       pvInfoList[i].depth,
13482                       buf );
13483
13484             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13485
13486             /* Print score/depth */
13487             blank = linelen > 0 && movelen > 0;
13488             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13489                 fprintf(f, "\n");
13490                 linelen = 0;
13491                 blank = 0;
13492             }
13493             if (blank) {
13494                 fprintf(f, " ");
13495                 linelen++;
13496             }
13497             fprintf(f, "%s", move_buffer);
13498             linelen += movelen;
13499         }
13500
13501         i++;
13502     }
13503
13504     /* Start a new line */
13505     if (linelen > 0) fprintf(f, "\n");
13506
13507     /* Print comments after last move */
13508     if (commentList[i] != NULL) {
13509         fprintf(f, "%s\n", commentList[i]);
13510     }
13511
13512     /* Print result */
13513     if (gameInfo.resultDetails != NULL &&
13514         gameInfo.resultDetails[0] != NULLCHAR) {
13515         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13516         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13517            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13518             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13519         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13520     } else {
13521         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13522     }
13523
13524     fclose(f);
13525     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13526     return TRUE;
13527 }
13528
13529 /* Save game in old style and close the file */
13530 int
13531 SaveGameOldStyle (FILE *f)
13532 {
13533     int i, offset;
13534     time_t tm;
13535
13536     tm = time((time_t *) NULL);
13537
13538     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13539     PrintOpponents(f);
13540
13541     if (backwardMostMove > 0 || startedFromSetupPosition) {
13542         fprintf(f, "\n[--------------\n");
13543         PrintPosition(f, backwardMostMove);
13544         fprintf(f, "--------------]\n");
13545     } else {
13546         fprintf(f, "\n");
13547     }
13548
13549     i = backwardMostMove;
13550     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13551
13552     while (i < forwardMostMove) {
13553         if (commentList[i] != NULL) {
13554             fprintf(f, "[%s]\n", commentList[i]);
13555         }
13556
13557         if ((i % 2) == 1) {
13558             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13559             i++;
13560         } else {
13561             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13562             i++;
13563             if (commentList[i] != NULL) {
13564                 fprintf(f, "\n");
13565                 continue;
13566             }
13567             if (i >= forwardMostMove) {
13568                 fprintf(f, "\n");
13569                 break;
13570             }
13571             fprintf(f, "%s\n", parseList[i]);
13572             i++;
13573         }
13574     }
13575
13576     if (commentList[i] != NULL) {
13577         fprintf(f, "[%s]\n", commentList[i]);
13578     }
13579
13580     /* This isn't really the old style, but it's close enough */
13581     if (gameInfo.resultDetails != NULL &&
13582         gameInfo.resultDetails[0] != NULLCHAR) {
13583         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13584                 gameInfo.resultDetails);
13585     } else {
13586         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13587     }
13588
13589     fclose(f);
13590     return TRUE;
13591 }
13592
13593 /* Save the current game to open file f and close the file */
13594 int
13595 SaveGame (FILE *f, int dummy, char *dummy2)
13596 {
13597     if (gameMode == EditPosition) EditPositionDone(TRUE);
13598     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13599     if (appData.oldSaveStyle)
13600       return SaveGameOldStyle(f);
13601     else
13602       return SaveGamePGN(f);
13603 }
13604
13605 /* Save the current position to the given file */
13606 int
13607 SavePositionToFile (char *filename)
13608 {
13609     FILE *f;
13610     char buf[MSG_SIZ];
13611
13612     if (strcmp(filename, "-") == 0) {
13613         return SavePosition(stdout, 0, NULL);
13614     } else {
13615         f = fopen(filename, "a");
13616         if (f == NULL) {
13617             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13618             DisplayError(buf, errno);
13619             return FALSE;
13620         } else {
13621             safeStrCpy(buf, lastMsg, MSG_SIZ);
13622             DisplayMessage(_("Waiting for access to save file"), "");
13623             flock(fileno(f), LOCK_EX); // [HGM] lock
13624             DisplayMessage(_("Saving position"), "");
13625             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13626             SavePosition(f, 0, NULL);
13627             DisplayMessage(buf, "");
13628             return TRUE;
13629         }
13630     }
13631 }
13632
13633 /* Save the current position to the given open file and close the file */
13634 int
13635 SavePosition (FILE *f, int dummy, char *dummy2)
13636 {
13637     time_t tm;
13638     char *fen;
13639
13640     if (gameMode == EditPosition) EditPositionDone(TRUE);
13641     if (appData.oldSaveStyle) {
13642         tm = time((time_t *) NULL);
13643
13644         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13645         PrintOpponents(f);
13646         fprintf(f, "[--------------\n");
13647         PrintPosition(f, currentMove);
13648         fprintf(f, "--------------]\n");
13649     } else {
13650         fen = PositionToFEN(currentMove, NULL, 1);
13651         fprintf(f, "%s\n", fen);
13652         free(fen);
13653     }
13654     fclose(f);
13655     return TRUE;
13656 }
13657
13658 void
13659 ReloadCmailMsgEvent (int unregister)
13660 {
13661 #if !WIN32
13662     static char *inFilename = NULL;
13663     static char *outFilename;
13664     int i;
13665     struct stat inbuf, outbuf;
13666     int status;
13667
13668     /* Any registered moves are unregistered if unregister is set, */
13669     /* i.e. invoked by the signal handler */
13670     if (unregister) {
13671         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13672             cmailMoveRegistered[i] = FALSE;
13673             if (cmailCommentList[i] != NULL) {
13674                 free(cmailCommentList[i]);
13675                 cmailCommentList[i] = NULL;
13676             }
13677         }
13678         nCmailMovesRegistered = 0;
13679     }
13680
13681     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13682         cmailResult[i] = CMAIL_NOT_RESULT;
13683     }
13684     nCmailResults = 0;
13685
13686     if (inFilename == NULL) {
13687         /* Because the filenames are static they only get malloced once  */
13688         /* and they never get freed                                      */
13689         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13690         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13691
13692         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13693         sprintf(outFilename, "%s.out", appData.cmailGameName);
13694     }
13695
13696     status = stat(outFilename, &outbuf);
13697     if (status < 0) {
13698         cmailMailedMove = FALSE;
13699     } else {
13700         status = stat(inFilename, &inbuf);
13701         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13702     }
13703
13704     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13705        counts the games, notes how each one terminated, etc.
13706
13707        It would be nice to remove this kludge and instead gather all
13708        the information while building the game list.  (And to keep it
13709        in the game list nodes instead of having a bunch of fixed-size
13710        parallel arrays.)  Note this will require getting each game's
13711        termination from the PGN tags, as the game list builder does
13712        not process the game moves.  --mann
13713        */
13714     cmailMsgLoaded = TRUE;
13715     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13716
13717     /* Load first game in the file or popup game menu */
13718     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13719
13720 #endif /* !WIN32 */
13721     return;
13722 }
13723
13724 int
13725 RegisterMove ()
13726 {
13727     FILE *f;
13728     char string[MSG_SIZ];
13729
13730     if (   cmailMailedMove
13731         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13732         return TRUE;            /* Allow free viewing  */
13733     }
13734
13735     /* Unregister move to ensure that we don't leave RegisterMove        */
13736     /* with the move registered when the conditions for registering no   */
13737     /* longer hold                                                       */
13738     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13739         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13740         nCmailMovesRegistered --;
13741
13742         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13743           {
13744               free(cmailCommentList[lastLoadGameNumber - 1]);
13745               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13746           }
13747     }
13748
13749     if (cmailOldMove == -1) {
13750         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13751         return FALSE;
13752     }
13753
13754     if (currentMove > cmailOldMove + 1) {
13755         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13756         return FALSE;
13757     }
13758
13759     if (currentMove < cmailOldMove) {
13760         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13761         return FALSE;
13762     }
13763
13764     if (forwardMostMove > currentMove) {
13765         /* Silently truncate extra moves */
13766         TruncateGame();
13767     }
13768
13769     if (   (currentMove == cmailOldMove + 1)
13770         || (   (currentMove == cmailOldMove)
13771             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13772                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13773         if (gameInfo.result != GameUnfinished) {
13774             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13775         }
13776
13777         if (commentList[currentMove] != NULL) {
13778             cmailCommentList[lastLoadGameNumber - 1]
13779               = StrSave(commentList[currentMove]);
13780         }
13781         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13782
13783         if (appData.debugMode)
13784           fprintf(debugFP, "Saving %s for game %d\n",
13785                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13786
13787         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13788
13789         f = fopen(string, "w");
13790         if (appData.oldSaveStyle) {
13791             SaveGameOldStyle(f); /* also closes the file */
13792
13793             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13794             f = fopen(string, "w");
13795             SavePosition(f, 0, NULL); /* also closes the file */
13796         } else {
13797             fprintf(f, "{--------------\n");
13798             PrintPosition(f, currentMove);
13799             fprintf(f, "--------------}\n\n");
13800
13801             SaveGame(f, 0, NULL); /* also closes the file*/
13802         }
13803
13804         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13805         nCmailMovesRegistered ++;
13806     } else if (nCmailGames == 1) {
13807         DisplayError(_("You have not made a move yet"), 0);
13808         return FALSE;
13809     }
13810
13811     return TRUE;
13812 }
13813
13814 void
13815 MailMoveEvent ()
13816 {
13817 #if !WIN32
13818     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13819     FILE *commandOutput;
13820     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13821     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13822     int nBuffers;
13823     int i;
13824     int archived;
13825     char *arcDir;
13826
13827     if (! cmailMsgLoaded) {
13828         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13829         return;
13830     }
13831
13832     if (nCmailGames == nCmailResults) {
13833         DisplayError(_("No unfinished games"), 0);
13834         return;
13835     }
13836
13837 #if CMAIL_PROHIBIT_REMAIL
13838     if (cmailMailedMove) {
13839       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);
13840         DisplayError(msg, 0);
13841         return;
13842     }
13843 #endif
13844
13845     if (! (cmailMailedMove || RegisterMove())) return;
13846
13847     if (   cmailMailedMove
13848         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13849       snprintf(string, MSG_SIZ, partCommandString,
13850                appData.debugMode ? " -v" : "", appData.cmailGameName);
13851         commandOutput = popen(string, "r");
13852
13853         if (commandOutput == NULL) {
13854             DisplayError(_("Failed to invoke cmail"), 0);
13855         } else {
13856             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13857                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13858             }
13859             if (nBuffers > 1) {
13860                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13861                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13862                 nBytes = MSG_SIZ - 1;
13863             } else {
13864                 (void) memcpy(msg, buffer, nBytes);
13865             }
13866             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13867
13868             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13869                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13870
13871                 archived = TRUE;
13872                 for (i = 0; i < nCmailGames; i ++) {
13873                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13874                         archived = FALSE;
13875                     }
13876                 }
13877                 if (   archived
13878                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13879                         != NULL)) {
13880                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13881                            arcDir,
13882                            appData.cmailGameName,
13883                            gameInfo.date);
13884                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13885                     cmailMsgLoaded = FALSE;
13886                 }
13887             }
13888
13889             DisplayInformation(msg);
13890             pclose(commandOutput);
13891         }
13892     } else {
13893         if ((*cmailMsg) != '\0') {
13894             DisplayInformation(cmailMsg);
13895         }
13896     }
13897
13898     return;
13899 #endif /* !WIN32 */
13900 }
13901
13902 char *
13903 CmailMsg ()
13904 {
13905 #if WIN32
13906     return NULL;
13907 #else
13908     int  prependComma = 0;
13909     char number[5];
13910     char string[MSG_SIZ];       /* Space for game-list */
13911     int  i;
13912
13913     if (!cmailMsgLoaded) return "";
13914
13915     if (cmailMailedMove) {
13916       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13917     } else {
13918         /* Create a list of games left */
13919       snprintf(string, MSG_SIZ, "[");
13920         for (i = 0; i < nCmailGames; i ++) {
13921             if (! (   cmailMoveRegistered[i]
13922                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13923                 if (prependComma) {
13924                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13925                 } else {
13926                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13927                     prependComma = 1;
13928                 }
13929
13930                 strcat(string, number);
13931             }
13932         }
13933         strcat(string, "]");
13934
13935         if (nCmailMovesRegistered + nCmailResults == 0) {
13936             switch (nCmailGames) {
13937               case 1:
13938                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13939                 break;
13940
13941               case 2:
13942                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13943                 break;
13944
13945               default:
13946                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13947                          nCmailGames);
13948                 break;
13949             }
13950         } else {
13951             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13952               case 1:
13953                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13954                          string);
13955                 break;
13956
13957               case 0:
13958                 if (nCmailResults == nCmailGames) {
13959                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13960                 } else {
13961                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13962                 }
13963                 break;
13964
13965               default:
13966                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13967                          string);
13968             }
13969         }
13970     }
13971     return cmailMsg;
13972 #endif /* WIN32 */
13973 }
13974
13975 void
13976 ResetGameEvent ()
13977 {
13978     if (gameMode == Training)
13979       SetTrainingModeOff();
13980
13981     Reset(TRUE, TRUE);
13982     cmailMsgLoaded = FALSE;
13983     if (appData.icsActive) {
13984       SendToICS(ics_prefix);
13985       SendToICS("refresh\n");
13986     }
13987 }
13988
13989 void
13990 ExitEvent (int status)
13991 {
13992     exiting++;
13993     if (exiting > 2) {
13994       /* Give up on clean exit */
13995       exit(status);
13996     }
13997     if (exiting > 1) {
13998       /* Keep trying for clean exit */
13999       return;
14000     }
14001
14002     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14003     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14004
14005     if (telnetISR != NULL) {
14006       RemoveInputSource(telnetISR);
14007     }
14008     if (icsPR != NoProc) {
14009       DestroyChildProcess(icsPR, TRUE);
14010     }
14011
14012     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14013     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14014
14015     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14016     /* make sure this other one finishes before killing it!                  */
14017     if(endingGame) { int count = 0;
14018         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14019         while(endingGame && count++ < 10) DoSleep(1);
14020         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14021     }
14022
14023     /* Kill off chess programs */
14024     if (first.pr != NoProc) {
14025         ExitAnalyzeMode();
14026
14027         DoSleep( appData.delayBeforeQuit );
14028         SendToProgram("quit\n", &first);
14029         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14030     }
14031     if (second.pr != NoProc) {
14032         DoSleep( appData.delayBeforeQuit );
14033         SendToProgram("quit\n", &second);
14034         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14035     }
14036     if (first.isr != NULL) {
14037         RemoveInputSource(first.isr);
14038     }
14039     if (second.isr != NULL) {
14040         RemoveInputSource(second.isr);
14041     }
14042
14043     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14044     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14045
14046     ShutDownFrontEnd();
14047     exit(status);
14048 }
14049
14050 void
14051 PauseEngine (ChessProgramState *cps)
14052 {
14053     SendToProgram("pause\n", cps);
14054     cps->pause = 2;
14055 }
14056
14057 void
14058 UnPauseEngine (ChessProgramState *cps)
14059 {
14060     SendToProgram("resume\n", cps);
14061     cps->pause = 1;
14062 }
14063
14064 void
14065 PauseEvent ()
14066 {
14067     if (appData.debugMode)
14068         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14069     if (pausing) {
14070         pausing = FALSE;
14071         ModeHighlight();
14072         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14073             StartClocks();
14074             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14075                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14076                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14077             }
14078             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14079             HandleMachineMove(stashedInputMove, stalledEngine);
14080             stalledEngine = NULL;
14081             return;
14082         }
14083         if (gameMode == MachinePlaysWhite ||
14084             gameMode == TwoMachinesPlay   ||
14085             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14086             if(first.pause)  UnPauseEngine(&first);
14087             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14088             if(second.pause) UnPauseEngine(&second);
14089             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14090             StartClocks();
14091         } else {
14092             DisplayBothClocks();
14093         }
14094         if (gameMode == PlayFromGameFile) {
14095             if (appData.timeDelay >= 0)
14096                 AutoPlayGameLoop();
14097         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14098             Reset(FALSE, TRUE);
14099             SendToICS(ics_prefix);
14100             SendToICS("refresh\n");
14101         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14102             ForwardInner(forwardMostMove);
14103         }
14104         pauseExamInvalid = FALSE;
14105     } else {
14106         switch (gameMode) {
14107           default:
14108             return;
14109           case IcsExamining:
14110             pauseExamForwardMostMove = forwardMostMove;
14111             pauseExamInvalid = FALSE;
14112             /* fall through */
14113           case IcsObserving:
14114           case IcsPlayingWhite:
14115           case IcsPlayingBlack:
14116             pausing = TRUE;
14117             ModeHighlight();
14118             return;
14119           case PlayFromGameFile:
14120             (void) StopLoadGameTimer();
14121             pausing = TRUE;
14122             ModeHighlight();
14123             break;
14124           case BeginningOfGame:
14125             if (appData.icsActive) return;
14126             /* else fall through */
14127           case MachinePlaysWhite:
14128           case MachinePlaysBlack:
14129           case TwoMachinesPlay:
14130             if (forwardMostMove == 0)
14131               return;           /* don't pause if no one has moved */
14132             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14133                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14134                 if(onMove->pause) {           // thinking engine can be paused
14135                     PauseEngine(onMove);      // do it
14136                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14137                         PauseEngine(onMove->other);
14138                     else
14139                         SendToProgram("easy\n", onMove->other);
14140                     StopClocks();
14141                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14142             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14143                 if(first.pause) {
14144                     PauseEngine(&first);
14145                     StopClocks();
14146                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14147             } else { // human on move, pause pondering by either method
14148                 if(first.pause)
14149                     PauseEngine(&first);
14150                 else if(appData.ponderNextMove)
14151                     SendToProgram("easy\n", &first);
14152                 StopClocks();
14153             }
14154             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14155           case AnalyzeMode:
14156             pausing = TRUE;
14157             ModeHighlight();
14158             break;
14159         }
14160     }
14161 }
14162
14163 void
14164 EditCommentEvent ()
14165 {
14166     char title[MSG_SIZ];
14167
14168     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14169       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14170     } else {
14171       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14172                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14173                parseList[currentMove - 1]);
14174     }
14175
14176     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14177 }
14178
14179
14180 void
14181 EditTagsEvent ()
14182 {
14183     char *tags = PGNTags(&gameInfo);
14184     bookUp = FALSE;
14185     EditTagsPopUp(tags, NULL);
14186     free(tags);
14187 }
14188
14189 void
14190 ToggleSecond ()
14191 {
14192   if(second.analyzing) {
14193     SendToProgram("exit\n", &second);
14194     second.analyzing = FALSE;
14195   } else {
14196     if (second.pr == NoProc) StartChessProgram(&second);
14197     InitChessProgram(&second, FALSE);
14198     FeedMovesToProgram(&second, currentMove);
14199
14200     SendToProgram("analyze\n", &second);
14201     second.analyzing = TRUE;
14202   }
14203 }
14204
14205 /* Toggle ShowThinking */
14206 void
14207 ToggleShowThinking()
14208 {
14209   appData.showThinking = !appData.showThinking;
14210   ShowThinkingEvent();
14211 }
14212
14213 int
14214 AnalyzeModeEvent ()
14215 {
14216     char buf[MSG_SIZ];
14217
14218     if (!first.analysisSupport) {
14219       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14220       DisplayError(buf, 0);
14221       return 0;
14222     }
14223     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14224     if (appData.icsActive) {
14225         if (gameMode != IcsObserving) {
14226           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14227             DisplayError(buf, 0);
14228             /* secure check */
14229             if (appData.icsEngineAnalyze) {
14230                 if (appData.debugMode)
14231                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14232                 ExitAnalyzeMode();
14233                 ModeHighlight();
14234             }
14235             return 0;
14236         }
14237         /* if enable, user wants to disable icsEngineAnalyze */
14238         if (appData.icsEngineAnalyze) {
14239                 ExitAnalyzeMode();
14240                 ModeHighlight();
14241                 return 0;
14242         }
14243         appData.icsEngineAnalyze = TRUE;
14244         if (appData.debugMode)
14245             fprintf(debugFP, "ICS engine analyze starting... \n");
14246     }
14247
14248     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14249     if (appData.noChessProgram || gameMode == AnalyzeMode)
14250       return 0;
14251
14252     if (gameMode != AnalyzeFile) {
14253         if (!appData.icsEngineAnalyze) {
14254                EditGameEvent();
14255                if (gameMode != EditGame) return 0;
14256         }
14257         if (!appData.showThinking) ToggleShowThinking();
14258         ResurrectChessProgram();
14259         SendToProgram("analyze\n", &first);
14260         first.analyzing = TRUE;
14261         /*first.maybeThinking = TRUE;*/
14262         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14263         EngineOutputPopUp();
14264     }
14265     if (!appData.icsEngineAnalyze) {
14266         gameMode = AnalyzeMode;
14267         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14268     }
14269     pausing = FALSE;
14270     ModeHighlight();
14271     SetGameInfo();
14272
14273     StartAnalysisClock();
14274     GetTimeMark(&lastNodeCountTime);
14275     lastNodeCount = 0;
14276     return 1;
14277 }
14278
14279 void
14280 AnalyzeFileEvent ()
14281 {
14282     if (appData.noChessProgram || gameMode == AnalyzeFile)
14283       return;
14284
14285     if (!first.analysisSupport) {
14286       char buf[MSG_SIZ];
14287       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14288       DisplayError(buf, 0);
14289       return;
14290     }
14291
14292     if (gameMode != AnalyzeMode) {
14293         keepInfo = 1; // mere annotating should not alter PGN tags
14294         EditGameEvent();
14295         keepInfo = 0;
14296         if (gameMode != EditGame) return;
14297         if (!appData.showThinking) ToggleShowThinking();
14298         ResurrectChessProgram();
14299         SendToProgram("analyze\n", &first);
14300         first.analyzing = TRUE;
14301         /*first.maybeThinking = TRUE;*/
14302         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14303         EngineOutputPopUp();
14304     }
14305     gameMode = AnalyzeFile;
14306     pausing = FALSE;
14307     ModeHighlight();
14308
14309     StartAnalysisClock();
14310     GetTimeMark(&lastNodeCountTime);
14311     lastNodeCount = 0;
14312     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14313     AnalysisPeriodicEvent(1);
14314 }
14315
14316 void
14317 MachineWhiteEvent ()
14318 {
14319     char buf[MSG_SIZ];
14320     char *bookHit = NULL;
14321
14322     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14323       return;
14324
14325
14326     if (gameMode == PlayFromGameFile ||
14327         gameMode == TwoMachinesPlay  ||
14328         gameMode == Training         ||
14329         gameMode == AnalyzeMode      ||
14330         gameMode == EndOfGame)
14331         EditGameEvent();
14332
14333     if (gameMode == EditPosition)
14334         EditPositionDone(TRUE);
14335
14336     if (!WhiteOnMove(currentMove)) {
14337         DisplayError(_("It is not White's turn"), 0);
14338         return;
14339     }
14340
14341     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14342       ExitAnalyzeMode();
14343
14344     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14345         gameMode == AnalyzeFile)
14346         TruncateGame();
14347
14348     ResurrectChessProgram();    /* in case it isn't running */
14349     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14350         gameMode = MachinePlaysWhite;
14351         ResetClocks();
14352     } else
14353     gameMode = MachinePlaysWhite;
14354     pausing = FALSE;
14355     ModeHighlight();
14356     SetGameInfo();
14357     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14358     DisplayTitle(buf);
14359     if (first.sendName) {
14360       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14361       SendToProgram(buf, &first);
14362     }
14363     if (first.sendTime) {
14364       if (first.useColors) {
14365         SendToProgram("black\n", &first); /*gnu kludge*/
14366       }
14367       SendTimeRemaining(&first, TRUE);
14368     }
14369     if (first.useColors) {
14370       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14371     }
14372     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14373     SetMachineThinkingEnables();
14374     first.maybeThinking = TRUE;
14375     StartClocks();
14376     firstMove = FALSE;
14377
14378     if (appData.autoFlipView && !flipView) {
14379       flipView = !flipView;
14380       DrawPosition(FALSE, NULL);
14381       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14382     }
14383
14384     if(bookHit) { // [HGM] book: simulate book reply
14385         static char bookMove[MSG_SIZ]; // a bit generous?
14386
14387         programStats.nodes = programStats.depth = programStats.time =
14388         programStats.score = programStats.got_only_move = 0;
14389         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14390
14391         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14392         strcat(bookMove, bookHit);
14393         HandleMachineMove(bookMove, &first);
14394     }
14395 }
14396
14397 void
14398 MachineBlackEvent ()
14399 {
14400   char buf[MSG_SIZ];
14401   char *bookHit = NULL;
14402
14403     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14404         return;
14405
14406
14407     if (gameMode == PlayFromGameFile ||
14408         gameMode == TwoMachinesPlay  ||
14409         gameMode == Training         ||
14410         gameMode == AnalyzeMode      ||
14411         gameMode == EndOfGame)
14412         EditGameEvent();
14413
14414     if (gameMode == EditPosition)
14415         EditPositionDone(TRUE);
14416
14417     if (WhiteOnMove(currentMove)) {
14418         DisplayError(_("It is not Black's turn"), 0);
14419         return;
14420     }
14421
14422     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14423       ExitAnalyzeMode();
14424
14425     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14426         gameMode == AnalyzeFile)
14427         TruncateGame();
14428
14429     ResurrectChessProgram();    /* in case it isn't running */
14430     gameMode = MachinePlaysBlack;
14431     pausing = FALSE;
14432     ModeHighlight();
14433     SetGameInfo();
14434     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14435     DisplayTitle(buf);
14436     if (first.sendName) {
14437       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14438       SendToProgram(buf, &first);
14439     }
14440     if (first.sendTime) {
14441       if (first.useColors) {
14442         SendToProgram("white\n", &first); /*gnu kludge*/
14443       }
14444       SendTimeRemaining(&first, FALSE);
14445     }
14446     if (first.useColors) {
14447       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14448     }
14449     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14450     SetMachineThinkingEnables();
14451     first.maybeThinking = TRUE;
14452     StartClocks();
14453
14454     if (appData.autoFlipView && flipView) {
14455       flipView = !flipView;
14456       DrawPosition(FALSE, NULL);
14457       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14458     }
14459     if(bookHit) { // [HGM] book: simulate book reply
14460         static char bookMove[MSG_SIZ]; // a bit generous?
14461
14462         programStats.nodes = programStats.depth = programStats.time =
14463         programStats.score = programStats.got_only_move = 0;
14464         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14465
14466         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14467         strcat(bookMove, bookHit);
14468         HandleMachineMove(bookMove, &first);
14469     }
14470 }
14471
14472
14473 void
14474 DisplayTwoMachinesTitle ()
14475 {
14476     char buf[MSG_SIZ];
14477     if (appData.matchGames > 0) {
14478         if(appData.tourneyFile[0]) {
14479           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14480                    gameInfo.white, _("vs."), gameInfo.black,
14481                    nextGame+1, appData.matchGames+1,
14482                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14483         } else
14484         if (first.twoMachinesColor[0] == 'w') {
14485           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14486                    gameInfo.white, _("vs."),  gameInfo.black,
14487                    first.matchWins, second.matchWins,
14488                    matchGame - 1 - (first.matchWins + second.matchWins));
14489         } else {
14490           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14491                    gameInfo.white, _("vs."), gameInfo.black,
14492                    second.matchWins, first.matchWins,
14493                    matchGame - 1 - (first.matchWins + second.matchWins));
14494         }
14495     } else {
14496       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14497     }
14498     DisplayTitle(buf);
14499 }
14500
14501 void
14502 SettingsMenuIfReady ()
14503 {
14504   if (second.lastPing != second.lastPong) {
14505     DisplayMessage("", _("Waiting for second chess program"));
14506     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14507     return;
14508   }
14509   ThawUI();
14510   DisplayMessage("", "");
14511   SettingsPopUp(&second);
14512 }
14513
14514 int
14515 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14516 {
14517     char buf[MSG_SIZ];
14518     if (cps->pr == NoProc) {
14519         StartChessProgram(cps);
14520         if (cps->protocolVersion == 1) {
14521           retry();
14522           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14523         } else {
14524           /* kludge: allow timeout for initial "feature" command */
14525           if(retry != TwoMachinesEventIfReady) FreezeUI();
14526           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14527           DisplayMessage("", buf);
14528           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14529         }
14530         return 1;
14531     }
14532     return 0;
14533 }
14534
14535 void
14536 TwoMachinesEvent P((void))
14537 {
14538     int i;
14539     char buf[MSG_SIZ];
14540     ChessProgramState *onmove;
14541     char *bookHit = NULL;
14542     static int stalling = 0;
14543     TimeMark now;
14544     long wait;
14545
14546     if (appData.noChessProgram) return;
14547
14548     switch (gameMode) {
14549       case TwoMachinesPlay:
14550         return;
14551       case MachinePlaysWhite:
14552       case MachinePlaysBlack:
14553         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14554             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14555             return;
14556         }
14557         /* fall through */
14558       case BeginningOfGame:
14559       case PlayFromGameFile:
14560       case EndOfGame:
14561         EditGameEvent();
14562         if (gameMode != EditGame) return;
14563         break;
14564       case EditPosition:
14565         EditPositionDone(TRUE);
14566         break;
14567       case AnalyzeMode:
14568       case AnalyzeFile:
14569         ExitAnalyzeMode();
14570         break;
14571       case EditGame:
14572       default:
14573         break;
14574     }
14575
14576 //    forwardMostMove = currentMove;
14577     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14578     startingEngine = TRUE;
14579
14580     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14581
14582     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14583     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14584       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14585       return;
14586     }
14587     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14588
14589     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14590                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14591         startingEngine = FALSE;
14592         DisplayError("second engine does not play this", 0);
14593         return;
14594     }
14595
14596     if(!stalling) {
14597       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14598       SendToProgram("force\n", &second);
14599       stalling = 1;
14600       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14601       return;
14602     }
14603     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14604     if(appData.matchPause>10000 || appData.matchPause<10)
14605                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14606     wait = SubtractTimeMarks(&now, &pauseStart);
14607     if(wait < appData.matchPause) {
14608         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14609         return;
14610     }
14611     // we are now committed to starting the game
14612     stalling = 0;
14613     DisplayMessage("", "");
14614     if (startedFromSetupPosition) {
14615         SendBoard(&second, backwardMostMove);
14616     if (appData.debugMode) {
14617         fprintf(debugFP, "Two Machines\n");
14618     }
14619     }
14620     for (i = backwardMostMove; i < forwardMostMove; i++) {
14621         SendMoveToProgram(i, &second);
14622     }
14623
14624     gameMode = TwoMachinesPlay;
14625     pausing = startingEngine = FALSE;
14626     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14627     SetGameInfo();
14628     DisplayTwoMachinesTitle();
14629     firstMove = TRUE;
14630     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14631         onmove = &first;
14632     } else {
14633         onmove = &second;
14634     }
14635     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14636     SendToProgram(first.computerString, &first);
14637     if (first.sendName) {
14638       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14639       SendToProgram(buf, &first);
14640     }
14641     SendToProgram(second.computerString, &second);
14642     if (second.sendName) {
14643       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14644       SendToProgram(buf, &second);
14645     }
14646
14647     ResetClocks();
14648     if (!first.sendTime || !second.sendTime) {
14649         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14650         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14651     }
14652     if (onmove->sendTime) {
14653       if (onmove->useColors) {
14654         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14655       }
14656       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14657     }
14658     if (onmove->useColors) {
14659       SendToProgram(onmove->twoMachinesColor, onmove);
14660     }
14661     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14662 //    SendToProgram("go\n", onmove);
14663     onmove->maybeThinking = TRUE;
14664     SetMachineThinkingEnables();
14665
14666     StartClocks();
14667
14668     if(bookHit) { // [HGM] book: simulate book reply
14669         static char bookMove[MSG_SIZ]; // a bit generous?
14670
14671         programStats.nodes = programStats.depth = programStats.time =
14672         programStats.score = programStats.got_only_move = 0;
14673         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14674
14675         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14676         strcat(bookMove, bookHit);
14677         savedMessage = bookMove; // args for deferred call
14678         savedState = onmove;
14679         ScheduleDelayedEvent(DeferredBookMove, 1);
14680     }
14681 }
14682
14683 void
14684 TrainingEvent ()
14685 {
14686     if (gameMode == Training) {
14687       SetTrainingModeOff();
14688       gameMode = PlayFromGameFile;
14689       DisplayMessage("", _("Training mode off"));
14690     } else {
14691       gameMode = Training;
14692       animateTraining = appData.animate;
14693
14694       /* make sure we are not already at the end of the game */
14695       if (currentMove < forwardMostMove) {
14696         SetTrainingModeOn();
14697         DisplayMessage("", _("Training mode on"));
14698       } else {
14699         gameMode = PlayFromGameFile;
14700         DisplayError(_("Already at end of game"), 0);
14701       }
14702     }
14703     ModeHighlight();
14704 }
14705
14706 void
14707 IcsClientEvent ()
14708 {
14709     if (!appData.icsActive) return;
14710     switch (gameMode) {
14711       case IcsPlayingWhite:
14712       case IcsPlayingBlack:
14713       case IcsObserving:
14714       case IcsIdle:
14715       case BeginningOfGame:
14716       case IcsExamining:
14717         return;
14718
14719       case EditGame:
14720         break;
14721
14722       case EditPosition:
14723         EditPositionDone(TRUE);
14724         break;
14725
14726       case AnalyzeMode:
14727       case AnalyzeFile:
14728         ExitAnalyzeMode();
14729         break;
14730
14731       default:
14732         EditGameEvent();
14733         break;
14734     }
14735
14736     gameMode = IcsIdle;
14737     ModeHighlight();
14738     return;
14739 }
14740
14741 void
14742 EditGameEvent ()
14743 {
14744     int i;
14745
14746     switch (gameMode) {
14747       case Training:
14748         SetTrainingModeOff();
14749         break;
14750       case MachinePlaysWhite:
14751       case MachinePlaysBlack:
14752       case BeginningOfGame:
14753         SendToProgram("force\n", &first);
14754         SetUserThinkingEnables();
14755         break;
14756       case PlayFromGameFile:
14757         (void) StopLoadGameTimer();
14758         if (gameFileFP != NULL) {
14759             gameFileFP = NULL;
14760         }
14761         break;
14762       case EditPosition:
14763         EditPositionDone(TRUE);
14764         break;
14765       case AnalyzeMode:
14766       case AnalyzeFile:
14767         ExitAnalyzeMode();
14768         SendToProgram("force\n", &first);
14769         break;
14770       case TwoMachinesPlay:
14771         GameEnds(EndOfFile, NULL, GE_PLAYER);
14772         ResurrectChessProgram();
14773         SetUserThinkingEnables();
14774         break;
14775       case EndOfGame:
14776         ResurrectChessProgram();
14777         break;
14778       case IcsPlayingBlack:
14779       case IcsPlayingWhite:
14780         DisplayError(_("Warning: You are still playing a game"), 0);
14781         break;
14782       case IcsObserving:
14783         DisplayError(_("Warning: You are still observing a game"), 0);
14784         break;
14785       case IcsExamining:
14786         DisplayError(_("Warning: You are still examining a game"), 0);
14787         break;
14788       case IcsIdle:
14789         break;
14790       case EditGame:
14791       default:
14792         return;
14793     }
14794
14795     pausing = FALSE;
14796     StopClocks();
14797     first.offeredDraw = second.offeredDraw = 0;
14798
14799     if (gameMode == PlayFromGameFile) {
14800         whiteTimeRemaining = timeRemaining[0][currentMove];
14801         blackTimeRemaining = timeRemaining[1][currentMove];
14802         DisplayTitle("");
14803     }
14804
14805     if (gameMode == MachinePlaysWhite ||
14806         gameMode == MachinePlaysBlack ||
14807         gameMode == TwoMachinesPlay ||
14808         gameMode == EndOfGame) {
14809         i = forwardMostMove;
14810         while (i > currentMove) {
14811             SendToProgram("undo\n", &first);
14812             i--;
14813         }
14814         if(!adjustedClock) {
14815         whiteTimeRemaining = timeRemaining[0][currentMove];
14816         blackTimeRemaining = timeRemaining[1][currentMove];
14817         DisplayBothClocks();
14818         }
14819         if (whiteFlag || blackFlag) {
14820             whiteFlag = blackFlag = 0;
14821         }
14822         DisplayTitle("");
14823     }
14824
14825     gameMode = EditGame;
14826     ModeHighlight();
14827     SetGameInfo();
14828 }
14829
14830
14831 void
14832 EditPositionEvent ()
14833 {
14834     if (gameMode == EditPosition) {
14835         EditGameEvent();
14836         return;
14837     }
14838
14839     EditGameEvent();
14840     if (gameMode != EditGame) return;
14841
14842     gameMode = EditPosition;
14843     ModeHighlight();
14844     SetGameInfo();
14845     if (currentMove > 0)
14846       CopyBoard(boards[0], boards[currentMove]);
14847
14848     blackPlaysFirst = !WhiteOnMove(currentMove);
14849     ResetClocks();
14850     currentMove = forwardMostMove = backwardMostMove = 0;
14851     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14852     DisplayMove(-1);
14853     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14854 }
14855
14856 void
14857 ExitAnalyzeMode ()
14858 {
14859     /* [DM] icsEngineAnalyze - possible call from other functions */
14860     if (appData.icsEngineAnalyze) {
14861         appData.icsEngineAnalyze = FALSE;
14862
14863         DisplayMessage("",_("Close ICS engine analyze..."));
14864     }
14865     if (first.analysisSupport && first.analyzing) {
14866       SendToBoth("exit\n");
14867       first.analyzing = second.analyzing = FALSE;
14868     }
14869     thinkOutput[0] = NULLCHAR;
14870 }
14871
14872 void
14873 EditPositionDone (Boolean fakeRights)
14874 {
14875     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14876
14877     startedFromSetupPosition = TRUE;
14878     InitChessProgram(&first, FALSE);
14879     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14880       boards[0][EP_STATUS] = EP_NONE;
14881       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14882       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14883         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14884         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14885       } else boards[0][CASTLING][2] = NoRights;
14886       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14887         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14888         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14889       } else boards[0][CASTLING][5] = NoRights;
14890       if(gameInfo.variant == VariantSChess) {
14891         int i;
14892         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14893           boards[0][VIRGIN][i] = 0;
14894           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14895           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14896         }
14897       }
14898     }
14899     SendToProgram("force\n", &first);
14900     if (blackPlaysFirst) {
14901         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14902         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14903         currentMove = forwardMostMove = backwardMostMove = 1;
14904         CopyBoard(boards[1], boards[0]);
14905     } else {
14906         currentMove = forwardMostMove = backwardMostMove = 0;
14907     }
14908     SendBoard(&first, forwardMostMove);
14909     if (appData.debugMode) {
14910         fprintf(debugFP, "EditPosDone\n");
14911     }
14912     DisplayTitle("");
14913     DisplayMessage("", "");
14914     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14915     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14916     gameMode = EditGame;
14917     ModeHighlight();
14918     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14919     ClearHighlights(); /* [AS] */
14920 }
14921
14922 /* Pause for `ms' milliseconds */
14923 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14924 void
14925 TimeDelay (long ms)
14926 {
14927     TimeMark m1, m2;
14928
14929     GetTimeMark(&m1);
14930     do {
14931         GetTimeMark(&m2);
14932     } while (SubtractTimeMarks(&m2, &m1) < ms);
14933 }
14934
14935 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14936 void
14937 SendMultiLineToICS (char *buf)
14938 {
14939     char temp[MSG_SIZ+1], *p;
14940     int len;
14941
14942     len = strlen(buf);
14943     if (len > MSG_SIZ)
14944       len = MSG_SIZ;
14945
14946     strncpy(temp, buf, len);
14947     temp[len] = 0;
14948
14949     p = temp;
14950     while (*p) {
14951         if (*p == '\n' || *p == '\r')
14952           *p = ' ';
14953         ++p;
14954     }
14955
14956     strcat(temp, "\n");
14957     SendToICS(temp);
14958     SendToPlayer(temp, strlen(temp));
14959 }
14960
14961 void
14962 SetWhiteToPlayEvent ()
14963 {
14964     if (gameMode == EditPosition) {
14965         blackPlaysFirst = FALSE;
14966         DisplayBothClocks();    /* works because currentMove is 0 */
14967     } else if (gameMode == IcsExamining) {
14968         SendToICS(ics_prefix);
14969         SendToICS("tomove white\n");
14970     }
14971 }
14972
14973 void
14974 SetBlackToPlayEvent ()
14975 {
14976     if (gameMode == EditPosition) {
14977         blackPlaysFirst = TRUE;
14978         currentMove = 1;        /* kludge */
14979         DisplayBothClocks();
14980         currentMove = 0;
14981     } else if (gameMode == IcsExamining) {
14982         SendToICS(ics_prefix);
14983         SendToICS("tomove black\n");
14984     }
14985 }
14986
14987 void
14988 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14989 {
14990     char buf[MSG_SIZ];
14991     ChessSquare piece = boards[0][y][x];
14992     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14993     static int lastVariant;
14994
14995     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14996
14997     switch (selection) {
14998       case ClearBoard:
14999         CopyBoard(currentBoard, boards[0]);
15000         CopyBoard(menuBoard, initialPosition);
15001         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15002             SendToICS(ics_prefix);
15003             SendToICS("bsetup clear\n");
15004         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15005             SendToICS(ics_prefix);
15006             SendToICS("clearboard\n");
15007         } else {
15008             int nonEmpty = 0;
15009             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15010                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15011                 for (y = 0; y < BOARD_HEIGHT; y++) {
15012                     if (gameMode == IcsExamining) {
15013                         if (boards[currentMove][y][x] != EmptySquare) {
15014                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15015                                     AAA + x, ONE + y);
15016                             SendToICS(buf);
15017                         }
15018                     } else {
15019                         if(boards[0][y][x] != p) nonEmpty++;
15020                         boards[0][y][x] = p;
15021                     }
15022                 }
15023             }
15024             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15025                 int r;
15026                 for(r = 0; r < BOARD_HEIGHT; r++) {
15027                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15028                     ChessSquare p = menuBoard[r][x];
15029                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15030                   }
15031                 }
15032                 DisplayMessage("Clicking clock again restores position", "");
15033                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15034                 if(!nonEmpty) { // asked to clear an empty board
15035                     CopyBoard(boards[0], menuBoard);
15036                 } else
15037                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15038                     CopyBoard(boards[0], initialPosition);
15039                 } else
15040                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15041                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15042                     CopyBoard(boards[0], erasedBoard);
15043                 } else
15044                     CopyBoard(erasedBoard, currentBoard);
15045
15046             }
15047         }
15048         if (gameMode == EditPosition) {
15049             DrawPosition(FALSE, boards[0]);
15050         }
15051         break;
15052
15053       case WhitePlay:
15054         SetWhiteToPlayEvent();
15055         break;
15056
15057       case BlackPlay:
15058         SetBlackToPlayEvent();
15059         break;
15060
15061       case EmptySquare:
15062         if (gameMode == IcsExamining) {
15063             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15064             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15065             SendToICS(buf);
15066         } else {
15067             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15068                 if(x == BOARD_LEFT-2) {
15069                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15070                     boards[0][y][1] = 0;
15071                 } else
15072                 if(x == BOARD_RGHT+1) {
15073                     if(y >= gameInfo.holdingsSize) break;
15074                     boards[0][y][BOARD_WIDTH-2] = 0;
15075                 } else break;
15076             }
15077             boards[0][y][x] = EmptySquare;
15078             DrawPosition(FALSE, boards[0]);
15079         }
15080         break;
15081
15082       case PromotePiece:
15083         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15084            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15085             selection = (ChessSquare) (PROMOTED piece);
15086         } else if(piece == EmptySquare) selection = WhiteSilver;
15087         else selection = (ChessSquare)((int)piece - 1);
15088         goto defaultlabel;
15089
15090       case DemotePiece:
15091         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15092            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15093             selection = (ChessSquare) (DEMOTED piece);
15094         } else if(piece == EmptySquare) selection = BlackSilver;
15095         else selection = (ChessSquare)((int)piece + 1);
15096         goto defaultlabel;
15097
15098       case WhiteQueen:
15099       case BlackQueen:
15100         if(gameInfo.variant == VariantShatranj ||
15101            gameInfo.variant == VariantXiangqi  ||
15102            gameInfo.variant == VariantCourier  ||
15103            gameInfo.variant == VariantASEAN    ||
15104            gameInfo.variant == VariantMakruk     )
15105             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15106         goto defaultlabel;
15107
15108       case WhiteKing:
15109       case BlackKing:
15110         if(gameInfo.variant == VariantXiangqi)
15111             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15112         if(gameInfo.variant == VariantKnightmate)
15113             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15114       default:
15115         defaultlabel:
15116         if (gameMode == IcsExamining) {
15117             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15118             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15119                      PieceToChar(selection), AAA + x, ONE + y);
15120             SendToICS(buf);
15121         } else {
15122             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15123                 int n;
15124                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15125                     n = PieceToNumber(selection - BlackPawn);
15126                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15127                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15128                     boards[0][BOARD_HEIGHT-1-n][1]++;
15129                 } else
15130                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15131                     n = PieceToNumber(selection);
15132                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15133                     boards[0][n][BOARD_WIDTH-1] = selection;
15134                     boards[0][n][BOARD_WIDTH-2]++;
15135                 }
15136             } else
15137             boards[0][y][x] = selection;
15138             DrawPosition(TRUE, boards[0]);
15139             ClearHighlights();
15140             fromX = fromY = -1;
15141         }
15142         break;
15143     }
15144 }
15145
15146
15147 void
15148 DropMenuEvent (ChessSquare selection, int x, int y)
15149 {
15150     ChessMove moveType;
15151
15152     switch (gameMode) {
15153       case IcsPlayingWhite:
15154       case MachinePlaysBlack:
15155         if (!WhiteOnMove(currentMove)) {
15156             DisplayMoveError(_("It is Black's turn"));
15157             return;
15158         }
15159         moveType = WhiteDrop;
15160         break;
15161       case IcsPlayingBlack:
15162       case MachinePlaysWhite:
15163         if (WhiteOnMove(currentMove)) {
15164             DisplayMoveError(_("It is White's turn"));
15165             return;
15166         }
15167         moveType = BlackDrop;
15168         break;
15169       case EditGame:
15170         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15171         break;
15172       default:
15173         return;
15174     }
15175
15176     if (moveType == BlackDrop && selection < BlackPawn) {
15177       selection = (ChessSquare) ((int) selection
15178                                  + (int) BlackPawn - (int) WhitePawn);
15179     }
15180     if (boards[currentMove][y][x] != EmptySquare) {
15181         DisplayMoveError(_("That square is occupied"));
15182         return;
15183     }
15184
15185     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15186 }
15187
15188 void
15189 AcceptEvent ()
15190 {
15191     /* Accept a pending offer of any kind from opponent */
15192
15193     if (appData.icsActive) {
15194         SendToICS(ics_prefix);
15195         SendToICS("accept\n");
15196     } else if (cmailMsgLoaded) {
15197         if (currentMove == cmailOldMove &&
15198             commentList[cmailOldMove] != NULL &&
15199             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15200                    "Black offers a draw" : "White offers a draw")) {
15201             TruncateGame();
15202             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15203             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15204         } else {
15205             DisplayError(_("There is no pending offer on this move"), 0);
15206             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15207         }
15208     } else {
15209         /* Not used for offers from chess program */
15210     }
15211 }
15212
15213 void
15214 DeclineEvent ()
15215 {
15216     /* Decline a pending offer of any kind from opponent */
15217
15218     if (appData.icsActive) {
15219         SendToICS(ics_prefix);
15220         SendToICS("decline\n");
15221     } else if (cmailMsgLoaded) {
15222         if (currentMove == cmailOldMove &&
15223             commentList[cmailOldMove] != NULL &&
15224             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15225                    "Black offers a draw" : "White offers a draw")) {
15226 #ifdef NOTDEF
15227             AppendComment(cmailOldMove, "Draw declined", TRUE);
15228             DisplayComment(cmailOldMove - 1, "Draw declined");
15229 #endif /*NOTDEF*/
15230         } else {
15231             DisplayError(_("There is no pending offer on this move"), 0);
15232         }
15233     } else {
15234         /* Not used for offers from chess program */
15235     }
15236 }
15237
15238 void
15239 RematchEvent ()
15240 {
15241     /* Issue ICS rematch command */
15242     if (appData.icsActive) {
15243         SendToICS(ics_prefix);
15244         SendToICS("rematch\n");
15245     }
15246 }
15247
15248 void
15249 CallFlagEvent ()
15250 {
15251     /* Call your opponent's flag (claim a win on time) */
15252     if (appData.icsActive) {
15253         SendToICS(ics_prefix);
15254         SendToICS("flag\n");
15255     } else {
15256         switch (gameMode) {
15257           default:
15258             return;
15259           case MachinePlaysWhite:
15260             if (whiteFlag) {
15261                 if (blackFlag)
15262                   GameEnds(GameIsDrawn, "Both players ran out of time",
15263                            GE_PLAYER);
15264                 else
15265                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15266             } else {
15267                 DisplayError(_("Your opponent is not out of time"), 0);
15268             }
15269             break;
15270           case MachinePlaysBlack:
15271             if (blackFlag) {
15272                 if (whiteFlag)
15273                   GameEnds(GameIsDrawn, "Both players ran out of time",
15274                            GE_PLAYER);
15275                 else
15276                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15277             } else {
15278                 DisplayError(_("Your opponent is not out of time"), 0);
15279             }
15280             break;
15281         }
15282     }
15283 }
15284
15285 void
15286 ClockClick (int which)
15287 {       // [HGM] code moved to back-end from winboard.c
15288         if(which) { // black clock
15289           if (gameMode == EditPosition || gameMode == IcsExamining) {
15290             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15291             SetBlackToPlayEvent();
15292           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15293           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15294           } else if (shiftKey) {
15295             AdjustClock(which, -1);
15296           } else if (gameMode == IcsPlayingWhite ||
15297                      gameMode == MachinePlaysBlack) {
15298             CallFlagEvent();
15299           }
15300         } else { // white clock
15301           if (gameMode == EditPosition || gameMode == IcsExamining) {
15302             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15303             SetWhiteToPlayEvent();
15304           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15305           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15306           } else if (shiftKey) {
15307             AdjustClock(which, -1);
15308           } else if (gameMode == IcsPlayingBlack ||
15309                    gameMode == MachinePlaysWhite) {
15310             CallFlagEvent();
15311           }
15312         }
15313 }
15314
15315 void
15316 DrawEvent ()
15317 {
15318     /* Offer draw or accept pending draw offer from opponent */
15319
15320     if (appData.icsActive) {
15321         /* Note: tournament rules require draw offers to be
15322            made after you make your move but before you punch
15323            your clock.  Currently ICS doesn't let you do that;
15324            instead, you immediately punch your clock after making
15325            a move, but you can offer a draw at any time. */
15326
15327         SendToICS(ics_prefix);
15328         SendToICS("draw\n");
15329         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15330     } else if (cmailMsgLoaded) {
15331         if (currentMove == cmailOldMove &&
15332             commentList[cmailOldMove] != NULL &&
15333             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15334                    "Black offers a draw" : "White offers a draw")) {
15335             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15336             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15337         } else if (currentMove == cmailOldMove + 1) {
15338             char *offer = WhiteOnMove(cmailOldMove) ?
15339               "White offers a draw" : "Black offers a draw";
15340             AppendComment(currentMove, offer, TRUE);
15341             DisplayComment(currentMove - 1, offer);
15342             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15343         } else {
15344             DisplayError(_("You must make your move before offering a draw"), 0);
15345             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15346         }
15347     } else if (first.offeredDraw) {
15348         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15349     } else {
15350         if (first.sendDrawOffers) {
15351             SendToProgram("draw\n", &first);
15352             userOfferedDraw = TRUE;
15353         }
15354     }
15355 }
15356
15357 void
15358 AdjournEvent ()
15359 {
15360     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15361
15362     if (appData.icsActive) {
15363         SendToICS(ics_prefix);
15364         SendToICS("adjourn\n");
15365     } else {
15366         /* Currently GNU Chess doesn't offer or accept Adjourns */
15367     }
15368 }
15369
15370
15371 void
15372 AbortEvent ()
15373 {
15374     /* Offer Abort or accept pending Abort offer from opponent */
15375
15376     if (appData.icsActive) {
15377         SendToICS(ics_prefix);
15378         SendToICS("abort\n");
15379     } else {
15380         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15381     }
15382 }
15383
15384 void
15385 ResignEvent ()
15386 {
15387     /* Resign.  You can do this even if it's not your turn. */
15388
15389     if (appData.icsActive) {
15390         SendToICS(ics_prefix);
15391         SendToICS("resign\n");
15392     } else {
15393         switch (gameMode) {
15394           case MachinePlaysWhite:
15395             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15396             break;
15397           case MachinePlaysBlack:
15398             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15399             break;
15400           case EditGame:
15401             if (cmailMsgLoaded) {
15402                 TruncateGame();
15403                 if (WhiteOnMove(cmailOldMove)) {
15404                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15405                 } else {
15406                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15407                 }
15408                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15409             }
15410             break;
15411           default:
15412             break;
15413         }
15414     }
15415 }
15416
15417
15418 void
15419 StopObservingEvent ()
15420 {
15421     /* Stop observing current games */
15422     SendToICS(ics_prefix);
15423     SendToICS("unobserve\n");
15424 }
15425
15426 void
15427 StopExaminingEvent ()
15428 {
15429     /* Stop observing current game */
15430     SendToICS(ics_prefix);
15431     SendToICS("unexamine\n");
15432 }
15433
15434 void
15435 ForwardInner (int target)
15436 {
15437     int limit; int oldSeekGraphUp = seekGraphUp;
15438
15439     if (appData.debugMode)
15440         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15441                 target, currentMove, forwardMostMove);
15442
15443     if (gameMode == EditPosition)
15444       return;
15445
15446     seekGraphUp = FALSE;
15447     MarkTargetSquares(1);
15448
15449     if (gameMode == PlayFromGameFile && !pausing)
15450       PauseEvent();
15451
15452     if (gameMode == IcsExamining && pausing)
15453       limit = pauseExamForwardMostMove;
15454     else
15455       limit = forwardMostMove;
15456
15457     if (target > limit) target = limit;
15458
15459     if (target > 0 && moveList[target - 1][0]) {
15460         int fromX, fromY, toX, toY;
15461         toX = moveList[target - 1][2] - AAA;
15462         toY = moveList[target - 1][3] - ONE;
15463         if (moveList[target - 1][1] == '@') {
15464             if (appData.highlightLastMove) {
15465                 SetHighlights(-1, -1, toX, toY);
15466             }
15467         } else {
15468             fromX = moveList[target - 1][0] - AAA;
15469             fromY = moveList[target - 1][1] - ONE;
15470             if (target == currentMove + 1) {
15471                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15472             }
15473             if (appData.highlightLastMove) {
15474                 SetHighlights(fromX, fromY, toX, toY);
15475             }
15476         }
15477     }
15478     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15479         gameMode == Training || gameMode == PlayFromGameFile ||
15480         gameMode == AnalyzeFile) {
15481         while (currentMove < target) {
15482             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15483             SendMoveToProgram(currentMove++, &first);
15484         }
15485     } else {
15486         currentMove = target;
15487     }
15488
15489     if (gameMode == EditGame || gameMode == EndOfGame) {
15490         whiteTimeRemaining = timeRemaining[0][currentMove];
15491         blackTimeRemaining = timeRemaining[1][currentMove];
15492     }
15493     DisplayBothClocks();
15494     DisplayMove(currentMove - 1);
15495     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15496     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15497     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15498         DisplayComment(currentMove - 1, commentList[currentMove]);
15499     }
15500     ClearMap(); // [HGM] exclude: invalidate map
15501 }
15502
15503
15504 void
15505 ForwardEvent ()
15506 {
15507     if (gameMode == IcsExamining && !pausing) {
15508         SendToICS(ics_prefix);
15509         SendToICS("forward\n");
15510     } else {
15511         ForwardInner(currentMove + 1);
15512     }
15513 }
15514
15515 void
15516 ToEndEvent ()
15517 {
15518     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15519         /* to optimze, we temporarily turn off analysis mode while we feed
15520          * the remaining moves to the engine. Otherwise we get analysis output
15521          * after each move.
15522          */
15523         if (first.analysisSupport) {
15524           SendToProgram("exit\nforce\n", &first);
15525           first.analyzing = FALSE;
15526         }
15527     }
15528
15529     if (gameMode == IcsExamining && !pausing) {
15530         SendToICS(ics_prefix);
15531         SendToICS("forward 999999\n");
15532     } else {
15533         ForwardInner(forwardMostMove);
15534     }
15535
15536     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15537         /* we have fed all the moves, so reactivate analysis mode */
15538         SendToProgram("analyze\n", &first);
15539         first.analyzing = TRUE;
15540         /*first.maybeThinking = TRUE;*/
15541         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15542     }
15543 }
15544
15545 void
15546 BackwardInner (int target)
15547 {
15548     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15549
15550     if (appData.debugMode)
15551         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15552                 target, currentMove, forwardMostMove);
15553
15554     if (gameMode == EditPosition) return;
15555     seekGraphUp = FALSE;
15556     MarkTargetSquares(1);
15557     if (currentMove <= backwardMostMove) {
15558         ClearHighlights();
15559         DrawPosition(full_redraw, boards[currentMove]);
15560         return;
15561     }
15562     if (gameMode == PlayFromGameFile && !pausing)
15563       PauseEvent();
15564
15565     if (moveList[target][0]) {
15566         int fromX, fromY, toX, toY;
15567         toX = moveList[target][2] - AAA;
15568         toY = moveList[target][3] - ONE;
15569         if (moveList[target][1] == '@') {
15570             if (appData.highlightLastMove) {
15571                 SetHighlights(-1, -1, toX, toY);
15572             }
15573         } else {
15574             fromX = moveList[target][0] - AAA;
15575             fromY = moveList[target][1] - ONE;
15576             if (target == currentMove - 1) {
15577                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15578             }
15579             if (appData.highlightLastMove) {
15580                 SetHighlights(fromX, fromY, toX, toY);
15581             }
15582         }
15583     }
15584     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15585         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15586         while (currentMove > target) {
15587             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15588                 // null move cannot be undone. Reload program with move history before it.
15589                 int i;
15590                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15591                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15592                 }
15593                 SendBoard(&first, i);
15594               if(second.analyzing) SendBoard(&second, i);
15595                 for(currentMove=i; currentMove<target; currentMove++) {
15596                     SendMoveToProgram(currentMove, &first);
15597                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15598                 }
15599                 break;
15600             }
15601             SendToBoth("undo\n");
15602             currentMove--;
15603         }
15604     } else {
15605         currentMove = target;
15606     }
15607
15608     if (gameMode == EditGame || gameMode == EndOfGame) {
15609         whiteTimeRemaining = timeRemaining[0][currentMove];
15610         blackTimeRemaining = timeRemaining[1][currentMove];
15611     }
15612     DisplayBothClocks();
15613     DisplayMove(currentMove - 1);
15614     DrawPosition(full_redraw, boards[currentMove]);
15615     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15616     // [HGM] PV info: routine tests if comment empty
15617     DisplayComment(currentMove - 1, commentList[currentMove]);
15618     ClearMap(); // [HGM] exclude: invalidate map
15619 }
15620
15621 void
15622 BackwardEvent ()
15623 {
15624     if (gameMode == IcsExamining && !pausing) {
15625         SendToICS(ics_prefix);
15626         SendToICS("backward\n");
15627     } else {
15628         BackwardInner(currentMove - 1);
15629     }
15630 }
15631
15632 void
15633 ToStartEvent ()
15634 {
15635     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15636         /* to optimize, we temporarily turn off analysis mode while we undo
15637          * all the moves. Otherwise we get analysis output after each undo.
15638          */
15639         if (first.analysisSupport) {
15640           SendToProgram("exit\nforce\n", &first);
15641           first.analyzing = FALSE;
15642         }
15643     }
15644
15645     if (gameMode == IcsExamining && !pausing) {
15646         SendToICS(ics_prefix);
15647         SendToICS("backward 999999\n");
15648     } else {
15649         BackwardInner(backwardMostMove);
15650     }
15651
15652     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15653         /* we have fed all the moves, so reactivate analysis mode */
15654         SendToProgram("analyze\n", &first);
15655         first.analyzing = TRUE;
15656         /*first.maybeThinking = TRUE;*/
15657         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15658     }
15659 }
15660
15661 void
15662 ToNrEvent (int to)
15663 {
15664   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15665   if (to >= forwardMostMove) to = forwardMostMove;
15666   if (to <= backwardMostMove) to = backwardMostMove;
15667   if (to < currentMove) {
15668     BackwardInner(to);
15669   } else {
15670     ForwardInner(to);
15671   }
15672 }
15673
15674 void
15675 RevertEvent (Boolean annotate)
15676 {
15677     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15678         return;
15679     }
15680     if (gameMode != IcsExamining) {
15681         DisplayError(_("You are not examining a game"), 0);
15682         return;
15683     }
15684     if (pausing) {
15685         DisplayError(_("You can't revert while pausing"), 0);
15686         return;
15687     }
15688     SendToICS(ics_prefix);
15689     SendToICS("revert\n");
15690 }
15691
15692 void
15693 RetractMoveEvent ()
15694 {
15695     switch (gameMode) {
15696       case MachinePlaysWhite:
15697       case MachinePlaysBlack:
15698         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15699             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15700             return;
15701         }
15702         if (forwardMostMove < 2) return;
15703         currentMove = forwardMostMove = forwardMostMove - 2;
15704         whiteTimeRemaining = timeRemaining[0][currentMove];
15705         blackTimeRemaining = timeRemaining[1][currentMove];
15706         DisplayBothClocks();
15707         DisplayMove(currentMove - 1);
15708         ClearHighlights();/*!! could figure this out*/
15709         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15710         SendToProgram("remove\n", &first);
15711         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15712         break;
15713
15714       case BeginningOfGame:
15715       default:
15716         break;
15717
15718       case IcsPlayingWhite:
15719       case IcsPlayingBlack:
15720         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15721             SendToICS(ics_prefix);
15722             SendToICS("takeback 2\n");
15723         } else {
15724             SendToICS(ics_prefix);
15725             SendToICS("takeback 1\n");
15726         }
15727         break;
15728     }
15729 }
15730
15731 void
15732 MoveNowEvent ()
15733 {
15734     ChessProgramState *cps;
15735
15736     switch (gameMode) {
15737       case MachinePlaysWhite:
15738         if (!WhiteOnMove(forwardMostMove)) {
15739             DisplayError(_("It is your turn"), 0);
15740             return;
15741         }
15742         cps = &first;
15743         break;
15744       case MachinePlaysBlack:
15745         if (WhiteOnMove(forwardMostMove)) {
15746             DisplayError(_("It is your turn"), 0);
15747             return;
15748         }
15749         cps = &first;
15750         break;
15751       case TwoMachinesPlay:
15752         if (WhiteOnMove(forwardMostMove) ==
15753             (first.twoMachinesColor[0] == 'w')) {
15754             cps = &first;
15755         } else {
15756             cps = &second;
15757         }
15758         break;
15759       case BeginningOfGame:
15760       default:
15761         return;
15762     }
15763     SendToProgram("?\n", cps);
15764 }
15765
15766 void
15767 TruncateGameEvent ()
15768 {
15769     EditGameEvent();
15770     if (gameMode != EditGame) return;
15771     TruncateGame();
15772 }
15773
15774 void
15775 TruncateGame ()
15776 {
15777     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15778     if (forwardMostMove > currentMove) {
15779         if (gameInfo.resultDetails != NULL) {
15780             free(gameInfo.resultDetails);
15781             gameInfo.resultDetails = NULL;
15782             gameInfo.result = GameUnfinished;
15783         }
15784         forwardMostMove = currentMove;
15785         HistorySet(parseList, backwardMostMove, forwardMostMove,
15786                    currentMove-1);
15787     }
15788 }
15789
15790 void
15791 HintEvent ()
15792 {
15793     if (appData.noChessProgram) return;
15794     switch (gameMode) {
15795       case MachinePlaysWhite:
15796         if (WhiteOnMove(forwardMostMove)) {
15797             DisplayError(_("Wait until your turn."), 0);
15798             return;
15799         }
15800         break;
15801       case BeginningOfGame:
15802       case MachinePlaysBlack:
15803         if (!WhiteOnMove(forwardMostMove)) {
15804             DisplayError(_("Wait until your turn."), 0);
15805             return;
15806         }
15807         break;
15808       default:
15809         DisplayError(_("No hint available"), 0);
15810         return;
15811     }
15812     SendToProgram("hint\n", &first);
15813     hintRequested = TRUE;
15814 }
15815
15816 void
15817 CreateBookEvent ()
15818 {
15819     ListGame * lg = (ListGame *) gameList.head;
15820     FILE *f, *g;
15821     int nItem;
15822     static int secondTime = FALSE;
15823
15824     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15825         DisplayError(_("Game list not loaded or empty"), 0);
15826         return;
15827     }
15828
15829     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15830         fclose(g);
15831         secondTime++;
15832         DisplayNote(_("Book file exists! Try again for overwrite."));
15833         return;
15834     }
15835
15836     creatingBook = TRUE;
15837     secondTime = FALSE;
15838
15839     /* Get list size */
15840     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15841         LoadGame(f, nItem, "", TRUE);
15842         AddGameToBook(TRUE);
15843         lg = (ListGame *) lg->node.succ;
15844     }
15845
15846     creatingBook = FALSE;
15847     FlushBook();
15848 }
15849
15850 void
15851 BookEvent ()
15852 {
15853     if (appData.noChessProgram) return;
15854     switch (gameMode) {
15855       case MachinePlaysWhite:
15856         if (WhiteOnMove(forwardMostMove)) {
15857             DisplayError(_("Wait until your turn."), 0);
15858             return;
15859         }
15860         break;
15861       case BeginningOfGame:
15862       case MachinePlaysBlack:
15863         if (!WhiteOnMove(forwardMostMove)) {
15864             DisplayError(_("Wait until your turn."), 0);
15865             return;
15866         }
15867         break;
15868       case EditPosition:
15869         EditPositionDone(TRUE);
15870         break;
15871       case TwoMachinesPlay:
15872         return;
15873       default:
15874         break;
15875     }
15876     SendToProgram("bk\n", &first);
15877     bookOutput[0] = NULLCHAR;
15878     bookRequested = TRUE;
15879 }
15880
15881 void
15882 AboutGameEvent ()
15883 {
15884     char *tags = PGNTags(&gameInfo);
15885     TagsPopUp(tags, CmailMsg());
15886     free(tags);
15887 }
15888
15889 /* end button procedures */
15890
15891 void
15892 PrintPosition (FILE *fp, int move)
15893 {
15894     int i, j;
15895
15896     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15897         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15898             char c = PieceToChar(boards[move][i][j]);
15899             fputc(c == 'x' ? '.' : c, fp);
15900             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15901         }
15902     }
15903     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15904       fprintf(fp, "white to play\n");
15905     else
15906       fprintf(fp, "black to play\n");
15907 }
15908
15909 void
15910 PrintOpponents (FILE *fp)
15911 {
15912     if (gameInfo.white != NULL) {
15913         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15914     } else {
15915         fprintf(fp, "\n");
15916     }
15917 }
15918
15919 /* Find last component of program's own name, using some heuristics */
15920 void
15921 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15922 {
15923     char *p, *q, c;
15924     int local = (strcmp(host, "localhost") == 0);
15925     while (!local && (p = strchr(prog, ';')) != NULL) {
15926         p++;
15927         while (*p == ' ') p++;
15928         prog = p;
15929     }
15930     if (*prog == '"' || *prog == '\'') {
15931         q = strchr(prog + 1, *prog);
15932     } else {
15933         q = strchr(prog, ' ');
15934     }
15935     if (q == NULL) q = prog + strlen(prog);
15936     p = q;
15937     while (p >= prog && *p != '/' && *p != '\\') p--;
15938     p++;
15939     if(p == prog && *p == '"') p++;
15940     c = *q; *q = 0;
15941     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15942     memcpy(buf, p, q - p);
15943     buf[q - p] = NULLCHAR;
15944     if (!local) {
15945         strcat(buf, "@");
15946         strcat(buf, host);
15947     }
15948 }
15949
15950 char *
15951 TimeControlTagValue ()
15952 {
15953     char buf[MSG_SIZ];
15954     if (!appData.clockMode) {
15955       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15956     } else if (movesPerSession > 0) {
15957       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15958     } else if (timeIncrement == 0) {
15959       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15960     } else {
15961       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15962     }
15963     return StrSave(buf);
15964 }
15965
15966 void
15967 SetGameInfo ()
15968 {
15969     /* This routine is used only for certain modes */
15970     VariantClass v = gameInfo.variant;
15971     ChessMove r = GameUnfinished;
15972     char *p = NULL;
15973
15974     if(keepInfo) return;
15975
15976     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15977         r = gameInfo.result;
15978         p = gameInfo.resultDetails;
15979         gameInfo.resultDetails = NULL;
15980     }
15981     ClearGameInfo(&gameInfo);
15982     gameInfo.variant = v;
15983
15984     switch (gameMode) {
15985       case MachinePlaysWhite:
15986         gameInfo.event = StrSave( appData.pgnEventHeader );
15987         gameInfo.site = StrSave(HostName());
15988         gameInfo.date = PGNDate();
15989         gameInfo.round = StrSave("-");
15990         gameInfo.white = StrSave(first.tidy);
15991         gameInfo.black = StrSave(UserName());
15992         gameInfo.timeControl = TimeControlTagValue();
15993         break;
15994
15995       case MachinePlaysBlack:
15996         gameInfo.event = StrSave( appData.pgnEventHeader );
15997         gameInfo.site = StrSave(HostName());
15998         gameInfo.date = PGNDate();
15999         gameInfo.round = StrSave("-");
16000         gameInfo.white = StrSave(UserName());
16001         gameInfo.black = StrSave(first.tidy);
16002         gameInfo.timeControl = TimeControlTagValue();
16003         break;
16004
16005       case TwoMachinesPlay:
16006         gameInfo.event = StrSave( appData.pgnEventHeader );
16007         gameInfo.site = StrSave(HostName());
16008         gameInfo.date = PGNDate();
16009         if (roundNr > 0) {
16010             char buf[MSG_SIZ];
16011             snprintf(buf, MSG_SIZ, "%d", roundNr);
16012             gameInfo.round = StrSave(buf);
16013         } else {
16014             gameInfo.round = StrSave("-");
16015         }
16016         if (first.twoMachinesColor[0] == 'w') {
16017             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16018             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16019         } else {
16020             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16021             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16022         }
16023         gameInfo.timeControl = TimeControlTagValue();
16024         break;
16025
16026       case EditGame:
16027         gameInfo.event = StrSave("Edited game");
16028         gameInfo.site = StrSave(HostName());
16029         gameInfo.date = PGNDate();
16030         gameInfo.round = StrSave("-");
16031         gameInfo.white = StrSave("-");
16032         gameInfo.black = StrSave("-");
16033         gameInfo.result = r;
16034         gameInfo.resultDetails = p;
16035         break;
16036
16037       case EditPosition:
16038         gameInfo.event = StrSave("Edited position");
16039         gameInfo.site = StrSave(HostName());
16040         gameInfo.date = PGNDate();
16041         gameInfo.round = StrSave("-");
16042         gameInfo.white = StrSave("-");
16043         gameInfo.black = StrSave("-");
16044         break;
16045
16046       case IcsPlayingWhite:
16047       case IcsPlayingBlack:
16048       case IcsObserving:
16049       case IcsExamining:
16050         break;
16051
16052       case PlayFromGameFile:
16053         gameInfo.event = StrSave("Game from non-PGN file");
16054         gameInfo.site = StrSave(HostName());
16055         gameInfo.date = PGNDate();
16056         gameInfo.round = StrSave("-");
16057         gameInfo.white = StrSave("?");
16058         gameInfo.black = StrSave("?");
16059         break;
16060
16061       default:
16062         break;
16063     }
16064 }
16065
16066 void
16067 ReplaceComment (int index, char *text)
16068 {
16069     int len;
16070     char *p;
16071     float score;
16072
16073     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16074        pvInfoList[index-1].depth == len &&
16075        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16076        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16077     while (*text == '\n') text++;
16078     len = strlen(text);
16079     while (len > 0 && text[len - 1] == '\n') len--;
16080
16081     if (commentList[index] != NULL)
16082       free(commentList[index]);
16083
16084     if (len == 0) {
16085         commentList[index] = NULL;
16086         return;
16087     }
16088   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16089       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16090       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16091     commentList[index] = (char *) malloc(len + 2);
16092     strncpy(commentList[index], text, len);
16093     commentList[index][len] = '\n';
16094     commentList[index][len + 1] = NULLCHAR;
16095   } else {
16096     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16097     char *p;
16098     commentList[index] = (char *) malloc(len + 7);
16099     safeStrCpy(commentList[index], "{\n", 3);
16100     safeStrCpy(commentList[index]+2, text, len+1);
16101     commentList[index][len+2] = NULLCHAR;
16102     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16103     strcat(commentList[index], "\n}\n");
16104   }
16105 }
16106
16107 void
16108 CrushCRs (char *text)
16109 {
16110   char *p = text;
16111   char *q = text;
16112   char ch;
16113
16114   do {
16115     ch = *p++;
16116     if (ch == '\r') continue;
16117     *q++ = ch;
16118   } while (ch != '\0');
16119 }
16120
16121 void
16122 AppendComment (int index, char *text, Boolean addBraces)
16123 /* addBraces  tells if we should add {} */
16124 {
16125     int oldlen, len;
16126     char *old;
16127
16128 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16129     if(addBraces == 3) addBraces = 0; else // force appending literally
16130     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16131
16132     CrushCRs(text);
16133     while (*text == '\n') text++;
16134     len = strlen(text);
16135     while (len > 0 && text[len - 1] == '\n') len--;
16136     text[len] = NULLCHAR;
16137
16138     if (len == 0) return;
16139
16140     if (commentList[index] != NULL) {
16141       Boolean addClosingBrace = addBraces;
16142         old = commentList[index];
16143         oldlen = strlen(old);
16144         while(commentList[index][oldlen-1] ==  '\n')
16145           commentList[index][--oldlen] = NULLCHAR;
16146         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16147         safeStrCpy(commentList[index], old, oldlen + len + 6);
16148         free(old);
16149         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16150         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16151           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16152           while (*text == '\n') { text++; len--; }
16153           commentList[index][--oldlen] = NULLCHAR;
16154       }
16155         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16156         else          strcat(commentList[index], "\n");
16157         strcat(commentList[index], text);
16158         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16159         else          strcat(commentList[index], "\n");
16160     } else {
16161         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16162         if(addBraces)
16163           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16164         else commentList[index][0] = NULLCHAR;
16165         strcat(commentList[index], text);
16166         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16167         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16168     }
16169 }
16170
16171 static char *
16172 FindStr (char * text, char * sub_text)
16173 {
16174     char * result = strstr( text, sub_text );
16175
16176     if( result != NULL ) {
16177         result += strlen( sub_text );
16178     }
16179
16180     return result;
16181 }
16182
16183 /* [AS] Try to extract PV info from PGN comment */
16184 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16185 char *
16186 GetInfoFromComment (int index, char * text)
16187 {
16188     char * sep = text, *p;
16189
16190     if( text != NULL && index > 0 ) {
16191         int score = 0;
16192         int depth = 0;
16193         int time = -1, sec = 0, deci;
16194         char * s_eval = FindStr( text, "[%eval " );
16195         char * s_emt = FindStr( text, "[%emt " );
16196 #if 0
16197         if( s_eval != NULL || s_emt != NULL ) {
16198 #else
16199         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16200 #endif
16201             /* New style */
16202             char delim;
16203
16204             if( s_eval != NULL ) {
16205                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16206                     return text;
16207                 }
16208
16209                 if( delim != ']' ) {
16210                     return text;
16211                 }
16212             }
16213
16214             if( s_emt != NULL ) {
16215             }
16216                 return text;
16217         }
16218         else {
16219             /* We expect something like: [+|-]nnn.nn/dd */
16220             int score_lo = 0;
16221
16222             if(*text != '{') return text; // [HGM] braces: must be normal comment
16223
16224             sep = strchr( text, '/' );
16225             if( sep == NULL || sep < (text+4) ) {
16226                 return text;
16227             }
16228
16229             p = text;
16230             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16231             if(p[1] == '(') { // comment starts with PV
16232                p = strchr(p, ')'); // locate end of PV
16233                if(p == NULL || sep < p+5) return text;
16234                // at this point we have something like "{(.*) +0.23/6 ..."
16235                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16236                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16237                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16238             }
16239             time = -1; sec = -1; deci = -1;
16240             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16241                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16242                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16243                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16244                 return text;
16245             }
16246
16247             if( score_lo < 0 || score_lo >= 100 ) {
16248                 return text;
16249             }
16250
16251             if(sec >= 0) time = 600*time + 10*sec; else
16252             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16253
16254             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16255
16256             /* [HGM] PV time: now locate end of PV info */
16257             while( *++sep >= '0' && *sep <= '9'); // strip depth
16258             if(time >= 0)
16259             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16260             if(sec >= 0)
16261             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16262             if(deci >= 0)
16263             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16264             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16265         }
16266
16267         if( depth <= 0 ) {
16268             return text;
16269         }
16270
16271         if( time < 0 ) {
16272             time = -1;
16273         }
16274
16275         pvInfoList[index-1].depth = depth;
16276         pvInfoList[index-1].score = score;
16277         pvInfoList[index-1].time  = 10*time; // centi-sec
16278         if(*sep == '}') *sep = 0; else *--sep = '{';
16279         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16280     }
16281     return sep;
16282 }
16283
16284 void
16285 SendToProgram (char *message, ChessProgramState *cps)
16286 {
16287     int count, outCount, error;
16288     char buf[MSG_SIZ];
16289
16290     if (cps->pr == NoProc) return;
16291     Attention(cps);
16292
16293     if (appData.debugMode) {
16294         TimeMark now;
16295         GetTimeMark(&now);
16296         fprintf(debugFP, "%ld >%-6s: %s",
16297                 SubtractTimeMarks(&now, &programStartTime),
16298                 cps->which, message);
16299         if(serverFP)
16300             fprintf(serverFP, "%ld >%-6s: %s",
16301                 SubtractTimeMarks(&now, &programStartTime),
16302                 cps->which, message), fflush(serverFP);
16303     }
16304
16305     count = strlen(message);
16306     outCount = OutputToProcess(cps->pr, message, count, &error);
16307     if (outCount < count && !exiting
16308                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16309       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16310       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16311         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16312             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16313                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16314                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16315                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16316             } else {
16317                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16318                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16319                 gameInfo.result = res;
16320             }
16321             gameInfo.resultDetails = StrSave(buf);
16322         }
16323         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16324         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16325     }
16326 }
16327
16328 void
16329 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16330 {
16331     char *end_str;
16332     char buf[MSG_SIZ];
16333     ChessProgramState *cps = (ChessProgramState *)closure;
16334
16335     if (isr != cps->isr) return; /* Killed intentionally */
16336     if (count <= 0) {
16337         if (count == 0) {
16338             RemoveInputSource(cps->isr);
16339             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16340                     _(cps->which), cps->program);
16341             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16342             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16343                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16344                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16345                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16346                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16347                 } else {
16348                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16349                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16350                     gameInfo.result = res;
16351                 }
16352                 gameInfo.resultDetails = StrSave(buf);
16353             }
16354             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16355             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16356         } else {
16357             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16358                     _(cps->which), cps->program);
16359             RemoveInputSource(cps->isr);
16360
16361             /* [AS] Program is misbehaving badly... kill it */
16362             if( count == -2 ) {
16363                 DestroyChildProcess( cps->pr, 9 );
16364                 cps->pr = NoProc;
16365             }
16366
16367             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16368         }
16369         return;
16370     }
16371
16372     if ((end_str = strchr(message, '\r')) != NULL)
16373       *end_str = NULLCHAR;
16374     if ((end_str = strchr(message, '\n')) != NULL)
16375       *end_str = NULLCHAR;
16376
16377     if (appData.debugMode) {
16378         TimeMark now; int print = 1;
16379         char *quote = ""; char c; int i;
16380
16381         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16382                 char start = message[0];
16383                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16384                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16385                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16386                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16387                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16388                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16389                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16390                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16391                    sscanf(message, "hint: %c", &c)!=1 &&
16392                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16393                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16394                     print = (appData.engineComments >= 2);
16395                 }
16396                 message[0] = start; // restore original message
16397         }
16398         if(print) {
16399                 GetTimeMark(&now);
16400                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16401                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16402                         quote,
16403                         message);
16404                 if(serverFP)
16405                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16406                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16407                         quote,
16408                         message), fflush(serverFP);
16409         }
16410     }
16411
16412     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16413     if (appData.icsEngineAnalyze) {
16414         if (strstr(message, "whisper") != NULL ||
16415              strstr(message, "kibitz") != NULL ||
16416             strstr(message, "tellics") != NULL) return;
16417     }
16418
16419     HandleMachineMove(message, cps);
16420 }
16421
16422
16423 void
16424 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16425 {
16426     char buf[MSG_SIZ];
16427     int seconds;
16428
16429     if( timeControl_2 > 0 ) {
16430         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16431             tc = timeControl_2;
16432         }
16433     }
16434     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16435     inc /= cps->timeOdds;
16436     st  /= cps->timeOdds;
16437
16438     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16439
16440     if (st > 0) {
16441       /* Set exact time per move, normally using st command */
16442       if (cps->stKludge) {
16443         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16444         seconds = st % 60;
16445         if (seconds == 0) {
16446           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16447         } else {
16448           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16449         }
16450       } else {
16451         snprintf(buf, MSG_SIZ, "st %d\n", st);
16452       }
16453     } else {
16454       /* Set conventional or incremental time control, using level command */
16455       if (seconds == 0) {
16456         /* Note old gnuchess bug -- minutes:seconds used to not work.
16457            Fixed in later versions, but still avoid :seconds
16458            when seconds is 0. */
16459         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16460       } else {
16461         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16462                  seconds, inc/1000.);
16463       }
16464     }
16465     SendToProgram(buf, cps);
16466
16467     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16468     /* Orthogonally, limit search to given depth */
16469     if (sd > 0) {
16470       if (cps->sdKludge) {
16471         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16472       } else {
16473         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16474       }
16475       SendToProgram(buf, cps);
16476     }
16477
16478     if(cps->nps >= 0) { /* [HGM] nps */
16479         if(cps->supportsNPS == FALSE)
16480           cps->nps = -1; // don't use if engine explicitly says not supported!
16481         else {
16482           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16483           SendToProgram(buf, cps);
16484         }
16485     }
16486 }
16487
16488 ChessProgramState *
16489 WhitePlayer ()
16490 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16491 {
16492     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16493        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16494         return &second;
16495     return &first;
16496 }
16497
16498 void
16499 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16500 {
16501     char message[MSG_SIZ];
16502     long time, otime;
16503
16504     /* Note: this routine must be called when the clocks are stopped
16505        or when they have *just* been set or switched; otherwise
16506        it will be off by the time since the current tick started.
16507     */
16508     if (machineWhite) {
16509         time = whiteTimeRemaining / 10;
16510         otime = blackTimeRemaining / 10;
16511     } else {
16512         time = blackTimeRemaining / 10;
16513         otime = whiteTimeRemaining / 10;
16514     }
16515     /* [HGM] translate opponent's time by time-odds factor */
16516     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16517
16518     if (time <= 0) time = 1;
16519     if (otime <= 0) otime = 1;
16520
16521     snprintf(message, MSG_SIZ, "time %ld\n", time);
16522     SendToProgram(message, cps);
16523
16524     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16525     SendToProgram(message, cps);
16526 }
16527
16528 char *
16529 EngineDefinedVariant (ChessProgramState *cps, int n)
16530 {   // return name of n-th unknown variant that engine supports
16531     static char buf[MSG_SIZ];
16532     char *p, *s = cps->variants;
16533     if(!s) return NULL;
16534     do { // parse string from variants feature
16535       VariantClass v;
16536         p = strchr(s, ',');
16537         if(p) *p = NULLCHAR;
16538       v = StringToVariant(s);
16539       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16540         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16541             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16542         }
16543         if(p) *p++ = ',';
16544         if(n < 0) return buf;
16545     } while(s = p);
16546     return NULL;
16547 }
16548
16549 int
16550 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16551 {
16552   char buf[MSG_SIZ];
16553   int len = strlen(name);
16554   int val;
16555
16556   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16557     (*p) += len + 1;
16558     sscanf(*p, "%d", &val);
16559     *loc = (val != 0);
16560     while (**p && **p != ' ')
16561       (*p)++;
16562     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16563     SendToProgram(buf, cps);
16564     return TRUE;
16565   }
16566   return FALSE;
16567 }
16568
16569 int
16570 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16571 {
16572   char buf[MSG_SIZ];
16573   int len = strlen(name);
16574   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16575     (*p) += len + 1;
16576     sscanf(*p, "%d", loc);
16577     while (**p && **p != ' ') (*p)++;
16578     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16579     SendToProgram(buf, cps);
16580     return TRUE;
16581   }
16582   return FALSE;
16583 }
16584
16585 int
16586 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16587 {
16588   char buf[MSG_SIZ];
16589   int len = strlen(name);
16590   if (strncmp((*p), name, len) == 0
16591       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16592     (*p) += len + 2;
16593     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16594     sscanf(*p, "%[^\"]", *loc);
16595     while (**p && **p != '\"') (*p)++;
16596     if (**p == '\"') (*p)++;
16597     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16598     SendToProgram(buf, cps);
16599     return TRUE;
16600   }
16601   return FALSE;
16602 }
16603
16604 int
16605 ParseOption (Option *opt, ChessProgramState *cps)
16606 // [HGM] options: process the string that defines an engine option, and determine
16607 // name, type, default value, and allowed value range
16608 {
16609         char *p, *q, buf[MSG_SIZ];
16610         int n, min = (-1)<<31, max = 1<<31, def;
16611
16612         if(p = strstr(opt->name, " -spin ")) {
16613             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16614             if(max < min) max = min; // enforce consistency
16615             if(def < min) def = min;
16616             if(def > max) def = max;
16617             opt->value = def;
16618             opt->min = min;
16619             opt->max = max;
16620             opt->type = Spin;
16621         } else if((p = strstr(opt->name, " -slider "))) {
16622             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16623             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16624             if(max < min) max = min; // enforce consistency
16625             if(def < min) def = min;
16626             if(def > max) def = max;
16627             opt->value = def;
16628             opt->min = min;
16629             opt->max = max;
16630             opt->type = Spin; // Slider;
16631         } else if((p = strstr(opt->name, " -string "))) {
16632             opt->textValue = p+9;
16633             opt->type = TextBox;
16634         } else if((p = strstr(opt->name, " -file "))) {
16635             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16636             opt->textValue = p+7;
16637             opt->type = FileName; // FileName;
16638         } else if((p = strstr(opt->name, " -path "))) {
16639             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16640             opt->textValue = p+7;
16641             opt->type = PathName; // PathName;
16642         } else if(p = strstr(opt->name, " -check ")) {
16643             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16644             opt->value = (def != 0);
16645             opt->type = CheckBox;
16646         } else if(p = strstr(opt->name, " -combo ")) {
16647             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16648             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16649             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16650             opt->value = n = 0;
16651             while(q = StrStr(q, " /// ")) {
16652                 n++; *q = 0;    // count choices, and null-terminate each of them
16653                 q += 5;
16654                 if(*q == '*') { // remember default, which is marked with * prefix
16655                     q++;
16656                     opt->value = n;
16657                 }
16658                 cps->comboList[cps->comboCnt++] = q;
16659             }
16660             cps->comboList[cps->comboCnt++] = NULL;
16661             opt->max = n + 1;
16662             opt->type = ComboBox;
16663         } else if(p = strstr(opt->name, " -button")) {
16664             opt->type = Button;
16665         } else if(p = strstr(opt->name, " -save")) {
16666             opt->type = SaveButton;
16667         } else return FALSE;
16668         *p = 0; // terminate option name
16669         // now look if the command-line options define a setting for this engine option.
16670         if(cps->optionSettings && cps->optionSettings[0])
16671             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16672         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16673           snprintf(buf, MSG_SIZ, "option %s", p);
16674                 if(p = strstr(buf, ",")) *p = 0;
16675                 if(q = strchr(buf, '=')) switch(opt->type) {
16676                     case ComboBox:
16677                         for(n=0; n<opt->max; n++)
16678                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16679                         break;
16680                     case TextBox:
16681                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16682                         break;
16683                     case Spin:
16684                     case CheckBox:
16685                         opt->value = atoi(q+1);
16686                     default:
16687                         break;
16688                 }
16689                 strcat(buf, "\n");
16690                 SendToProgram(buf, cps);
16691         }
16692         return TRUE;
16693 }
16694
16695 void
16696 FeatureDone (ChessProgramState *cps, int val)
16697 {
16698   DelayedEventCallback cb = GetDelayedEvent();
16699   if ((cb == InitBackEnd3 && cps == &first) ||
16700       (cb == SettingsMenuIfReady && cps == &second) ||
16701       (cb == LoadEngine) ||
16702       (cb == TwoMachinesEventIfReady)) {
16703     CancelDelayedEvent();
16704     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16705   }
16706   cps->initDone = val;
16707   if(val) cps->reload = FALSE;
16708 }
16709
16710 /* Parse feature command from engine */
16711 void
16712 ParseFeatures (char *args, ChessProgramState *cps)
16713 {
16714   char *p = args;
16715   char *q = NULL;
16716   int val;
16717   char buf[MSG_SIZ];
16718
16719   for (;;) {
16720     while (*p == ' ') p++;
16721     if (*p == NULLCHAR) return;
16722
16723     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16724     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16725     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16726     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16727     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16728     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16729     if (BoolFeature(&p, "reuse", &val, cps)) {
16730       /* Engine can disable reuse, but can't enable it if user said no */
16731       if (!val) cps->reuse = FALSE;
16732       continue;
16733     }
16734     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16735     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16736       if (gameMode == TwoMachinesPlay) {
16737         DisplayTwoMachinesTitle();
16738       } else {
16739         DisplayTitle("");
16740       }
16741       continue;
16742     }
16743     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16744     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16745     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16746     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16747     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16748     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16749     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16750     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16751     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16752     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16753     if (IntFeature(&p, "done", &val, cps)) {
16754       FeatureDone(cps, val);
16755       continue;
16756     }
16757     /* Added by Tord: */
16758     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16759     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16760     /* End of additions by Tord */
16761
16762     /* [HGM] added features: */
16763     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16764     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16765     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16766     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16767     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16768     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16769     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16770     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16771         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16772         FREE(cps->option[cps->nrOptions].name);
16773         cps->option[cps->nrOptions].name = q; q = NULL;
16774         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16775           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16776             SendToProgram(buf, cps);
16777             continue;
16778         }
16779         if(cps->nrOptions >= MAX_OPTIONS) {
16780             cps->nrOptions--;
16781             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16782             DisplayError(buf, 0);
16783         }
16784         continue;
16785     }
16786     /* End of additions by HGM */
16787
16788     /* unknown feature: complain and skip */
16789     q = p;
16790     while (*q && *q != '=') q++;
16791     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16792     SendToProgram(buf, cps);
16793     p = q;
16794     if (*p == '=') {
16795       p++;
16796       if (*p == '\"') {
16797         p++;
16798         while (*p && *p != '\"') p++;
16799         if (*p == '\"') p++;
16800       } else {
16801         while (*p && *p != ' ') p++;
16802       }
16803     }
16804   }
16805
16806 }
16807
16808 void
16809 PeriodicUpdatesEvent (int newState)
16810 {
16811     if (newState == appData.periodicUpdates)
16812       return;
16813
16814     appData.periodicUpdates=newState;
16815
16816     /* Display type changes, so update it now */
16817 //    DisplayAnalysis();
16818
16819     /* Get the ball rolling again... */
16820     if (newState) {
16821         AnalysisPeriodicEvent(1);
16822         StartAnalysisClock();
16823     }
16824 }
16825
16826 void
16827 PonderNextMoveEvent (int newState)
16828 {
16829     if (newState == appData.ponderNextMove) return;
16830     if (gameMode == EditPosition) EditPositionDone(TRUE);
16831     if (newState) {
16832         SendToProgram("hard\n", &first);
16833         if (gameMode == TwoMachinesPlay) {
16834             SendToProgram("hard\n", &second);
16835         }
16836     } else {
16837         SendToProgram("easy\n", &first);
16838         thinkOutput[0] = NULLCHAR;
16839         if (gameMode == TwoMachinesPlay) {
16840             SendToProgram("easy\n", &second);
16841         }
16842     }
16843     appData.ponderNextMove = newState;
16844 }
16845
16846 void
16847 NewSettingEvent (int option, int *feature, char *command, int value)
16848 {
16849     char buf[MSG_SIZ];
16850
16851     if (gameMode == EditPosition) EditPositionDone(TRUE);
16852     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16853     if(feature == NULL || *feature) SendToProgram(buf, &first);
16854     if (gameMode == TwoMachinesPlay) {
16855         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16856     }
16857 }
16858
16859 void
16860 ShowThinkingEvent ()
16861 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16862 {
16863     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16864     int newState = appData.showThinking
16865         // [HGM] thinking: other features now need thinking output as well
16866         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16867
16868     if (oldState == newState) return;
16869     oldState = newState;
16870     if (gameMode == EditPosition) EditPositionDone(TRUE);
16871     if (oldState) {
16872         SendToProgram("post\n", &first);
16873         if (gameMode == TwoMachinesPlay) {
16874             SendToProgram("post\n", &second);
16875         }
16876     } else {
16877         SendToProgram("nopost\n", &first);
16878         thinkOutput[0] = NULLCHAR;
16879         if (gameMode == TwoMachinesPlay) {
16880             SendToProgram("nopost\n", &second);
16881         }
16882     }
16883 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16884 }
16885
16886 void
16887 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16888 {
16889   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16890   if (pr == NoProc) return;
16891   AskQuestion(title, question, replyPrefix, pr);
16892 }
16893
16894 void
16895 TypeInEvent (char firstChar)
16896 {
16897     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16898         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16899         gameMode == AnalyzeMode || gameMode == EditGame ||
16900         gameMode == EditPosition || gameMode == IcsExamining ||
16901         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16902         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16903                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16904                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16905         gameMode == Training) PopUpMoveDialog(firstChar);
16906 }
16907
16908 void
16909 TypeInDoneEvent (char *move)
16910 {
16911         Board board;
16912         int n, fromX, fromY, toX, toY;
16913         char promoChar;
16914         ChessMove moveType;
16915
16916         // [HGM] FENedit
16917         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16918                 EditPositionPasteFEN(move);
16919                 return;
16920         }
16921         // [HGM] movenum: allow move number to be typed in any mode
16922         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16923           ToNrEvent(2*n-1);
16924           return;
16925         }
16926         // undocumented kludge: allow command-line option to be typed in!
16927         // (potentially fatal, and does not implement the effect of the option.)
16928         // should only be used for options that are values on which future decisions will be made,
16929         // and definitely not on options that would be used during initialization.
16930         if(strstr(move, "!!! -") == move) {
16931             ParseArgsFromString(move+4);
16932             return;
16933         }
16934
16935       if (gameMode != EditGame && currentMove != forwardMostMove &&
16936         gameMode != Training) {
16937         DisplayMoveError(_("Displayed move is not current"));
16938       } else {
16939         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16940           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16941         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16942         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16943           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16944           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16945         } else {
16946           DisplayMoveError(_("Could not parse move"));
16947         }
16948       }
16949 }
16950
16951 void
16952 DisplayMove (int moveNumber)
16953 {
16954     char message[MSG_SIZ];
16955     char res[MSG_SIZ];
16956     char cpThinkOutput[MSG_SIZ];
16957
16958     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16959
16960     if (moveNumber == forwardMostMove - 1 ||
16961         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16962
16963         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16964
16965         if (strchr(cpThinkOutput, '\n')) {
16966             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16967         }
16968     } else {
16969         *cpThinkOutput = NULLCHAR;
16970     }
16971
16972     /* [AS] Hide thinking from human user */
16973     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16974         *cpThinkOutput = NULLCHAR;
16975         if( thinkOutput[0] != NULLCHAR ) {
16976             int i;
16977
16978             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16979                 cpThinkOutput[i] = '.';
16980             }
16981             cpThinkOutput[i] = NULLCHAR;
16982             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16983         }
16984     }
16985
16986     if (moveNumber == forwardMostMove - 1 &&
16987         gameInfo.resultDetails != NULL) {
16988         if (gameInfo.resultDetails[0] == NULLCHAR) {
16989           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16990         } else {
16991           snprintf(res, MSG_SIZ, " {%s} %s",
16992                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16993         }
16994     } else {
16995         res[0] = NULLCHAR;
16996     }
16997
16998     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16999         DisplayMessage(res, cpThinkOutput);
17000     } else {
17001       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17002                 WhiteOnMove(moveNumber) ? " " : ".. ",
17003                 parseList[moveNumber], res);
17004         DisplayMessage(message, cpThinkOutput);
17005     }
17006 }
17007
17008 void
17009 DisplayComment (int moveNumber, char *text)
17010 {
17011     char title[MSG_SIZ];
17012
17013     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17014       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17015     } else {
17016       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17017               WhiteOnMove(moveNumber) ? " " : ".. ",
17018               parseList[moveNumber]);
17019     }
17020     if (text != NULL && (appData.autoDisplayComment || commentUp))
17021         CommentPopUp(title, text);
17022 }
17023
17024 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17025  * might be busy thinking or pondering.  It can be omitted if your
17026  * gnuchess is configured to stop thinking immediately on any user
17027  * input.  However, that gnuchess feature depends on the FIONREAD
17028  * ioctl, which does not work properly on some flavors of Unix.
17029  */
17030 void
17031 Attention (ChessProgramState *cps)
17032 {
17033 #if ATTENTION
17034     if (!cps->useSigint) return;
17035     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17036     switch (gameMode) {
17037       case MachinePlaysWhite:
17038       case MachinePlaysBlack:
17039       case TwoMachinesPlay:
17040       case IcsPlayingWhite:
17041       case IcsPlayingBlack:
17042       case AnalyzeMode:
17043       case AnalyzeFile:
17044         /* Skip if we know it isn't thinking */
17045         if (!cps->maybeThinking) return;
17046         if (appData.debugMode)
17047           fprintf(debugFP, "Interrupting %s\n", cps->which);
17048         InterruptChildProcess(cps->pr);
17049         cps->maybeThinking = FALSE;
17050         break;
17051       default:
17052         break;
17053     }
17054 #endif /*ATTENTION*/
17055 }
17056
17057 int
17058 CheckFlags ()
17059 {
17060     if (whiteTimeRemaining <= 0) {
17061         if (!whiteFlag) {
17062             whiteFlag = TRUE;
17063             if (appData.icsActive) {
17064                 if (appData.autoCallFlag &&
17065                     gameMode == IcsPlayingBlack && !blackFlag) {
17066                   SendToICS(ics_prefix);
17067                   SendToICS("flag\n");
17068                 }
17069             } else {
17070                 if (blackFlag) {
17071                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17072                 } else {
17073                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17074                     if (appData.autoCallFlag) {
17075                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17076                         return TRUE;
17077                     }
17078                 }
17079             }
17080         }
17081     }
17082     if (blackTimeRemaining <= 0) {
17083         if (!blackFlag) {
17084             blackFlag = TRUE;
17085             if (appData.icsActive) {
17086                 if (appData.autoCallFlag &&
17087                     gameMode == IcsPlayingWhite && !whiteFlag) {
17088                   SendToICS(ics_prefix);
17089                   SendToICS("flag\n");
17090                 }
17091             } else {
17092                 if (whiteFlag) {
17093                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17094                 } else {
17095                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17096                     if (appData.autoCallFlag) {
17097                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17098                         return TRUE;
17099                     }
17100                 }
17101             }
17102         }
17103     }
17104     return FALSE;
17105 }
17106
17107 void
17108 CheckTimeControl ()
17109 {
17110     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17111         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17112
17113     /*
17114      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17115      */
17116     if ( !WhiteOnMove(forwardMostMove) ) {
17117         /* White made time control */
17118         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17119         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17120         /* [HGM] time odds: correct new time quota for time odds! */
17121                                             / WhitePlayer()->timeOdds;
17122         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17123     } else {
17124         lastBlack -= blackTimeRemaining;
17125         /* Black made time control */
17126         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17127                                             / WhitePlayer()->other->timeOdds;
17128         lastWhite = whiteTimeRemaining;
17129     }
17130 }
17131
17132 void
17133 DisplayBothClocks ()
17134 {
17135     int wom = gameMode == EditPosition ?
17136       !blackPlaysFirst : WhiteOnMove(currentMove);
17137     DisplayWhiteClock(whiteTimeRemaining, wom);
17138     DisplayBlackClock(blackTimeRemaining, !wom);
17139 }
17140
17141
17142 /* Timekeeping seems to be a portability nightmare.  I think everyone
17143    has ftime(), but I'm really not sure, so I'm including some ifdefs
17144    to use other calls if you don't.  Clocks will be less accurate if
17145    you have neither ftime nor gettimeofday.
17146 */
17147
17148 /* VS 2008 requires the #include outside of the function */
17149 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17150 #include <sys/timeb.h>
17151 #endif
17152
17153 /* Get the current time as a TimeMark */
17154 void
17155 GetTimeMark (TimeMark *tm)
17156 {
17157 #if HAVE_GETTIMEOFDAY
17158
17159     struct timeval timeVal;
17160     struct timezone timeZone;
17161
17162     gettimeofday(&timeVal, &timeZone);
17163     tm->sec = (long) timeVal.tv_sec;
17164     tm->ms = (int) (timeVal.tv_usec / 1000L);
17165
17166 #else /*!HAVE_GETTIMEOFDAY*/
17167 #if HAVE_FTIME
17168
17169 // include <sys/timeb.h> / moved to just above start of function
17170     struct timeb timeB;
17171
17172     ftime(&timeB);
17173     tm->sec = (long) timeB.time;
17174     tm->ms = (int) timeB.millitm;
17175
17176 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17177     tm->sec = (long) time(NULL);
17178     tm->ms = 0;
17179 #endif
17180 #endif
17181 }
17182
17183 /* Return the difference in milliseconds between two
17184    time marks.  We assume the difference will fit in a long!
17185 */
17186 long
17187 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17188 {
17189     return 1000L*(tm2->sec - tm1->sec) +
17190            (long) (tm2->ms - tm1->ms);
17191 }
17192
17193
17194 /*
17195  * Code to manage the game clocks.
17196  *
17197  * In tournament play, black starts the clock and then white makes a move.
17198  * We give the human user a slight advantage if he is playing white---the
17199  * clocks don't run until he makes his first move, so it takes zero time.
17200  * Also, we don't account for network lag, so we could get out of sync
17201  * with GNU Chess's clock -- but then, referees are always right.
17202  */
17203
17204 static TimeMark tickStartTM;
17205 static long intendedTickLength;
17206
17207 long
17208 NextTickLength (long timeRemaining)
17209 {
17210     long nominalTickLength, nextTickLength;
17211
17212     if (timeRemaining > 0L && timeRemaining <= 10000L)
17213       nominalTickLength = 100L;
17214     else
17215       nominalTickLength = 1000L;
17216     nextTickLength = timeRemaining % nominalTickLength;
17217     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17218
17219     return nextTickLength;
17220 }
17221
17222 /* Adjust clock one minute up or down */
17223 void
17224 AdjustClock (Boolean which, int dir)
17225 {
17226     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17227     if(which) blackTimeRemaining += 60000*dir;
17228     else      whiteTimeRemaining += 60000*dir;
17229     DisplayBothClocks();
17230     adjustedClock = TRUE;
17231 }
17232
17233 /* Stop clocks and reset to a fresh time control */
17234 void
17235 ResetClocks ()
17236 {
17237     (void) StopClockTimer();
17238     if (appData.icsActive) {
17239         whiteTimeRemaining = blackTimeRemaining = 0;
17240     } else if (searchTime) {
17241         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17242         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17243     } else { /* [HGM] correct new time quote for time odds */
17244         whiteTC = blackTC = fullTimeControlString;
17245         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17246         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17247     }
17248     if (whiteFlag || blackFlag) {
17249         DisplayTitle("");
17250         whiteFlag = blackFlag = FALSE;
17251     }
17252     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17253     DisplayBothClocks();
17254     adjustedClock = FALSE;
17255 }
17256
17257 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17258
17259 /* Decrement running clock by amount of time that has passed */
17260 void
17261 DecrementClocks ()
17262 {
17263     long timeRemaining;
17264     long lastTickLength, fudge;
17265     TimeMark now;
17266
17267     if (!appData.clockMode) return;
17268     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17269
17270     GetTimeMark(&now);
17271
17272     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17273
17274     /* Fudge if we woke up a little too soon */
17275     fudge = intendedTickLength - lastTickLength;
17276     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17277
17278     if (WhiteOnMove(forwardMostMove)) {
17279         if(whiteNPS >= 0) lastTickLength = 0;
17280         timeRemaining = whiteTimeRemaining -= lastTickLength;
17281         if(timeRemaining < 0 && !appData.icsActive) {
17282             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17283             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17284                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17285                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17286             }
17287         }
17288         DisplayWhiteClock(whiteTimeRemaining - fudge,
17289                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17290     } else {
17291         if(blackNPS >= 0) lastTickLength = 0;
17292         timeRemaining = blackTimeRemaining -= lastTickLength;
17293         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17294             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17295             if(suddenDeath) {
17296                 blackStartMove = forwardMostMove;
17297                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17298             }
17299         }
17300         DisplayBlackClock(blackTimeRemaining - fudge,
17301                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17302     }
17303     if (CheckFlags()) return;
17304
17305     if(twoBoards) { // count down secondary board's clocks as well
17306         activePartnerTime -= lastTickLength;
17307         partnerUp = 1;
17308         if(activePartner == 'W')
17309             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17310         else
17311             DisplayBlackClock(activePartnerTime, TRUE);
17312         partnerUp = 0;
17313     }
17314
17315     tickStartTM = now;
17316     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17317     StartClockTimer(intendedTickLength);
17318
17319     /* if the time remaining has fallen below the alarm threshold, sound the
17320      * alarm. if the alarm has sounded and (due to a takeback or time control
17321      * with increment) the time remaining has increased to a level above the
17322      * threshold, reset the alarm so it can sound again.
17323      */
17324
17325     if (appData.icsActive && appData.icsAlarm) {
17326
17327         /* make sure we are dealing with the user's clock */
17328         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17329                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17330            )) return;
17331
17332         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17333             alarmSounded = FALSE;
17334         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17335             PlayAlarmSound();
17336             alarmSounded = TRUE;
17337         }
17338     }
17339 }
17340
17341
17342 /* A player has just moved, so stop the previously running
17343    clock and (if in clock mode) start the other one.
17344    We redisplay both clocks in case we're in ICS mode, because
17345    ICS gives us an update to both clocks after every move.
17346    Note that this routine is called *after* forwardMostMove
17347    is updated, so the last fractional tick must be subtracted
17348    from the color that is *not* on move now.
17349 */
17350 void
17351 SwitchClocks (int newMoveNr)
17352 {
17353     long lastTickLength;
17354     TimeMark now;
17355     int flagged = FALSE;
17356
17357     GetTimeMark(&now);
17358
17359     if (StopClockTimer() && appData.clockMode) {
17360         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17361         if (!WhiteOnMove(forwardMostMove)) {
17362             if(blackNPS >= 0) lastTickLength = 0;
17363             blackTimeRemaining -= lastTickLength;
17364            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17365 //         if(pvInfoList[forwardMostMove].time == -1)
17366                  pvInfoList[forwardMostMove].time =               // use GUI time
17367                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17368         } else {
17369            if(whiteNPS >= 0) lastTickLength = 0;
17370            whiteTimeRemaining -= lastTickLength;
17371            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17372 //         if(pvInfoList[forwardMostMove].time == -1)
17373                  pvInfoList[forwardMostMove].time =
17374                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17375         }
17376         flagged = CheckFlags();
17377     }
17378     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17379     CheckTimeControl();
17380
17381     if (flagged || !appData.clockMode) return;
17382
17383     switch (gameMode) {
17384       case MachinePlaysBlack:
17385       case MachinePlaysWhite:
17386       case BeginningOfGame:
17387         if (pausing) return;
17388         break;
17389
17390       case EditGame:
17391       case PlayFromGameFile:
17392       case IcsExamining:
17393         return;
17394
17395       default:
17396         break;
17397     }
17398
17399     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17400         if(WhiteOnMove(forwardMostMove))
17401              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17402         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17403     }
17404
17405     tickStartTM = now;
17406     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17407       whiteTimeRemaining : blackTimeRemaining);
17408     StartClockTimer(intendedTickLength);
17409 }
17410
17411
17412 /* Stop both clocks */
17413 void
17414 StopClocks ()
17415 {
17416     long lastTickLength;
17417     TimeMark now;
17418
17419     if (!StopClockTimer()) return;
17420     if (!appData.clockMode) return;
17421
17422     GetTimeMark(&now);
17423
17424     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17425     if (WhiteOnMove(forwardMostMove)) {
17426         if(whiteNPS >= 0) lastTickLength = 0;
17427         whiteTimeRemaining -= lastTickLength;
17428         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17429     } else {
17430         if(blackNPS >= 0) lastTickLength = 0;
17431         blackTimeRemaining -= lastTickLength;
17432         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17433     }
17434     CheckFlags();
17435 }
17436
17437 /* Start clock of player on move.  Time may have been reset, so
17438    if clock is already running, stop and restart it. */
17439 void
17440 StartClocks ()
17441 {
17442     (void) StopClockTimer(); /* in case it was running already */
17443     DisplayBothClocks();
17444     if (CheckFlags()) return;
17445
17446     if (!appData.clockMode) return;
17447     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17448
17449     GetTimeMark(&tickStartTM);
17450     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17451       whiteTimeRemaining : blackTimeRemaining);
17452
17453    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17454     whiteNPS = blackNPS = -1;
17455     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17456        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17457         whiteNPS = first.nps;
17458     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17459        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17460         blackNPS = first.nps;
17461     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17462         whiteNPS = second.nps;
17463     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17464         blackNPS = second.nps;
17465     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17466
17467     StartClockTimer(intendedTickLength);
17468 }
17469
17470 char *
17471 TimeString (long ms)
17472 {
17473     long second, minute, hour, day;
17474     char *sign = "";
17475     static char buf[32];
17476
17477     if (ms > 0 && ms <= 9900) {
17478       /* convert milliseconds to tenths, rounding up */
17479       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17480
17481       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17482       return buf;
17483     }
17484
17485     /* convert milliseconds to seconds, rounding up */
17486     /* use floating point to avoid strangeness of integer division
17487        with negative dividends on many machines */
17488     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17489
17490     if (second < 0) {
17491         sign = "-";
17492         second = -second;
17493     }
17494
17495     day = second / (60 * 60 * 24);
17496     second = second % (60 * 60 * 24);
17497     hour = second / (60 * 60);
17498     second = second % (60 * 60);
17499     minute = second / 60;
17500     second = second % 60;
17501
17502     if (day > 0)
17503       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17504               sign, day, hour, minute, second);
17505     else if (hour > 0)
17506       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17507     else
17508       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17509
17510     return buf;
17511 }
17512
17513
17514 /*
17515  * This is necessary because some C libraries aren't ANSI C compliant yet.
17516  */
17517 char *
17518 StrStr (char *string, char *match)
17519 {
17520     int i, length;
17521
17522     length = strlen(match);
17523
17524     for (i = strlen(string) - length; i >= 0; i--, string++)
17525       if (!strncmp(match, string, length))
17526         return string;
17527
17528     return NULL;
17529 }
17530
17531 char *
17532 StrCaseStr (char *string, char *match)
17533 {
17534     int i, j, length;
17535
17536     length = strlen(match);
17537
17538     for (i = strlen(string) - length; i >= 0; i--, string++) {
17539         for (j = 0; j < length; j++) {
17540             if (ToLower(match[j]) != ToLower(string[j]))
17541               break;
17542         }
17543         if (j == length) return string;
17544     }
17545
17546     return NULL;
17547 }
17548
17549 #ifndef _amigados
17550 int
17551 StrCaseCmp (char *s1, char *s2)
17552 {
17553     char c1, c2;
17554
17555     for (;;) {
17556         c1 = ToLower(*s1++);
17557         c2 = ToLower(*s2++);
17558         if (c1 > c2) return 1;
17559         if (c1 < c2) return -1;
17560         if (c1 == NULLCHAR) return 0;
17561     }
17562 }
17563
17564
17565 int
17566 ToLower (int c)
17567 {
17568     return isupper(c) ? tolower(c) : c;
17569 }
17570
17571
17572 int
17573 ToUpper (int c)
17574 {
17575     return islower(c) ? toupper(c) : c;
17576 }
17577 #endif /* !_amigados    */
17578
17579 char *
17580 StrSave (char *s)
17581 {
17582   char *ret;
17583
17584   if ((ret = (char *) malloc(strlen(s) + 1)))
17585     {
17586       safeStrCpy(ret, s, strlen(s)+1);
17587     }
17588   return ret;
17589 }
17590
17591 char *
17592 StrSavePtr (char *s, char **savePtr)
17593 {
17594     if (*savePtr) {
17595         free(*savePtr);
17596     }
17597     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17598       safeStrCpy(*savePtr, s, strlen(s)+1);
17599     }
17600     return(*savePtr);
17601 }
17602
17603 char *
17604 PGNDate ()
17605 {
17606     time_t clock;
17607     struct tm *tm;
17608     char buf[MSG_SIZ];
17609
17610     clock = time((time_t *)NULL);
17611     tm = localtime(&clock);
17612     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17613             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17614     return StrSave(buf);
17615 }
17616
17617
17618 char *
17619 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17620 {
17621     int i, j, fromX, fromY, toX, toY;
17622     int whiteToPlay;
17623     char buf[MSG_SIZ];
17624     char *p, *q;
17625     int emptycount;
17626     ChessSquare piece;
17627
17628     whiteToPlay = (gameMode == EditPosition) ?
17629       !blackPlaysFirst : (move % 2 == 0);
17630     p = buf;
17631
17632     /* Piece placement data */
17633     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17634         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17635         emptycount = 0;
17636         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17637             if (boards[move][i][j] == EmptySquare) {
17638                 emptycount++;
17639             } else { ChessSquare piece = boards[move][i][j];
17640                 if (emptycount > 0) {
17641                     if(emptycount<10) /* [HGM] can be >= 10 */
17642                         *p++ = '0' + emptycount;
17643                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17644                     emptycount = 0;
17645                 }
17646                 if(PieceToChar(piece) == '+') {
17647                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17648                     *p++ = '+';
17649                     piece = (ChessSquare)(CHUDEMOTED piece);
17650                 }
17651                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17652                 if(p[-1] == '~') {
17653                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17654                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17655                     *p++ = '~';
17656                 }
17657             }
17658         }
17659         if (emptycount > 0) {
17660             if(emptycount<10) /* [HGM] can be >= 10 */
17661                 *p++ = '0' + emptycount;
17662             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17663             emptycount = 0;
17664         }
17665         *p++ = '/';
17666     }
17667     *(p - 1) = ' ';
17668
17669     /* [HGM] print Crazyhouse or Shogi holdings */
17670     if( gameInfo.holdingsWidth ) {
17671         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17672         q = p;
17673         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17674             piece = boards[move][i][BOARD_WIDTH-1];
17675             if( piece != EmptySquare )
17676               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17677                   *p++ = PieceToChar(piece);
17678         }
17679         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17680             piece = boards[move][BOARD_HEIGHT-i-1][0];
17681             if( piece != EmptySquare )
17682               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17683                   *p++ = PieceToChar(piece);
17684         }
17685
17686         if( q == p ) *p++ = '-';
17687         *p++ = ']';
17688         *p++ = ' ';
17689     }
17690
17691     /* Active color */
17692     *p++ = whiteToPlay ? 'w' : 'b';
17693     *p++ = ' ';
17694
17695   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17696     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17697   } else {
17698   if(nrCastlingRights) {
17699      q = p;
17700      if(appData.fischerCastling) {
17701        /* [HGM] write directly from rights */
17702            if(boards[move][CASTLING][2] != NoRights &&
17703               boards[move][CASTLING][0] != NoRights   )
17704                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17705            if(boards[move][CASTLING][2] != NoRights &&
17706               boards[move][CASTLING][1] != NoRights   )
17707                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17708            if(boards[move][CASTLING][5] != NoRights &&
17709               boards[move][CASTLING][3] != NoRights   )
17710                 *p++ = boards[move][CASTLING][3] + AAA;
17711            if(boards[move][CASTLING][5] != NoRights &&
17712               boards[move][CASTLING][4] != NoRights   )
17713                 *p++ = boards[move][CASTLING][4] + AAA;
17714      } else {
17715
17716         /* [HGM] write true castling rights */
17717         if( nrCastlingRights == 6 ) {
17718             int q, k=0;
17719             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17720                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17721             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17722                  boards[move][CASTLING][2] != NoRights  );
17723             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17724                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17725                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17726                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17727                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17728             }
17729             if(q) *p++ = 'Q';
17730             k = 0;
17731             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17732                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17733             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17734                  boards[move][CASTLING][5] != NoRights  );
17735             if(gameInfo.variant == VariantSChess) {
17736                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17737                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17738                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17739                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17740             }
17741             if(q) *p++ = 'q';
17742         }
17743      }
17744      if (q == p) *p++ = '-'; /* No castling rights */
17745      *p++ = ' ';
17746   }
17747
17748   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17749      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17750      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17751     /* En passant target square */
17752     if (move > backwardMostMove) {
17753         fromX = moveList[move - 1][0] - AAA;
17754         fromY = moveList[move - 1][1] - ONE;
17755         toX = moveList[move - 1][2] - AAA;
17756         toY = moveList[move - 1][3] - ONE;
17757         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17758             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17759             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17760             fromX == toX) {
17761             /* 2-square pawn move just happened */
17762             *p++ = toX + AAA;
17763             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17764         } else {
17765             *p++ = '-';
17766         }
17767     } else if(move == backwardMostMove) {
17768         // [HGM] perhaps we should always do it like this, and forget the above?
17769         if((signed char)boards[move][EP_STATUS] >= 0) {
17770             *p++ = boards[move][EP_STATUS] + AAA;
17771             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17772         } else {
17773             *p++ = '-';
17774         }
17775     } else {
17776         *p++ = '-';
17777     }
17778     *p++ = ' ';
17779   }
17780   }
17781
17782     if(moveCounts)
17783     {   int i = 0, j=move;
17784
17785         /* [HGM] find reversible plies */
17786         if (appData.debugMode) { int k;
17787             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17788             for(k=backwardMostMove; k<=forwardMostMove; k++)
17789                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17790
17791         }
17792
17793         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17794         if( j == backwardMostMove ) i += initialRulePlies;
17795         sprintf(p, "%d ", i);
17796         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17797
17798         /* Fullmove number */
17799         sprintf(p, "%d", (move / 2) + 1);
17800     } else *--p = NULLCHAR;
17801
17802     return StrSave(buf);
17803 }
17804
17805 Boolean
17806 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17807 {
17808     int i, j, k, w=0, subst=0, shuffle=0;
17809     char *p, c;
17810     int emptycount, virgin[BOARD_FILES];
17811     ChessSquare piece;
17812
17813     p = fen;
17814
17815     /* Piece placement data */
17816     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17817         j = 0;
17818         for (;;) {
17819             if (*p == '/' || *p == ' ' || *p == '[' ) {
17820                 if(j > w) w = j;
17821                 emptycount = gameInfo.boardWidth - j;
17822                 while (emptycount--)
17823                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17824                 if (*p == '/') p++;
17825                 else if(autoSize) { // we stumbled unexpectedly into end of board
17826                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17827                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17828                     }
17829                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17830                 }
17831                 break;
17832 #if(BOARD_FILES >= 10)*0
17833             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17834                 p++; emptycount=10;
17835                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17836                 while (emptycount--)
17837                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17838 #endif
17839             } else if (*p == '*') {
17840                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17841             } else if (isdigit(*p)) {
17842                 emptycount = *p++ - '0';
17843                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17844                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17845                 while (emptycount--)
17846                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17847             } else if (*p == '<') {
17848                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17849                 else if (i != 0 || !shuffle) return FALSE;
17850                 p++;
17851             } else if (shuffle && *p == '>') {
17852                 p++; // for now ignore closing shuffle range, and assume rank-end
17853             } else if (*p == '?') {
17854                 if (j >= gameInfo.boardWidth) return FALSE;
17855                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17856                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17857             } else if (*p == '+' || isalpha(*p)) {
17858                 if (j >= gameInfo.boardWidth) return FALSE;
17859                 if(*p=='+') {
17860                     piece = CharToPiece(*++p);
17861                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17862                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17863                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17864                 } else piece = CharToPiece(*p++);
17865
17866                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17867                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17868                     piece = (ChessSquare) (PROMOTED piece);
17869                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17870                     p++;
17871                 }
17872                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17873             } else {
17874                 return FALSE;
17875             }
17876         }
17877     }
17878     while (*p == '/' || *p == ' ') p++;
17879
17880     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17881
17882     /* [HGM] by default clear Crazyhouse holdings, if present */
17883     if(gameInfo.holdingsWidth) {
17884        for(i=0; i<BOARD_HEIGHT; i++) {
17885            board[i][0]             = EmptySquare; /* black holdings */
17886            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17887            board[i][1]             = (ChessSquare) 0; /* black counts */
17888            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17889        }
17890     }
17891
17892     /* [HGM] look for Crazyhouse holdings here */
17893     while(*p==' ') p++;
17894     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17895         int swap=0, wcnt=0, bcnt=0;
17896         if(*p == '[') p++;
17897         if(*p == '<') swap++, p++;
17898         if(*p == '-' ) p++; /* empty holdings */ else {
17899             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17900             /* if we would allow FEN reading to set board size, we would   */
17901             /* have to add holdings and shift the board read so far here   */
17902             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17903                 p++;
17904                 if((int) piece >= (int) BlackPawn ) {
17905                     i = (int)piece - (int)BlackPawn;
17906                     i = PieceToNumber((ChessSquare)i);
17907                     if( i >= gameInfo.holdingsSize ) return FALSE;
17908                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17909                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17910                     bcnt++;
17911                 } else {
17912                     i = (int)piece - (int)WhitePawn;
17913                     i = PieceToNumber((ChessSquare)i);
17914                     if( i >= gameInfo.holdingsSize ) return FALSE;
17915                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17916                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17917                     wcnt++;
17918                 }
17919             }
17920             if(subst) { // substitute back-rank question marks by holdings pieces
17921                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17922                     int k, m, n = bcnt + 1;
17923                     if(board[0][j] == ClearBoard) {
17924                         if(!wcnt) return FALSE;
17925                         n = rand() % wcnt;
17926                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17927                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17928                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17929                             break;
17930                         }
17931                     }
17932                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17933                         if(!bcnt) return FALSE;
17934                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17935                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17936                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17937                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17938                             break;
17939                         }
17940                     }
17941                 }
17942                 subst = 0;
17943             }
17944         }
17945         if(*p == ']') p++;
17946     }
17947
17948     if(subst) return FALSE; // substitution requested, but no holdings
17949
17950     while(*p == ' ') p++;
17951
17952     /* Active color */
17953     c = *p++;
17954     if(appData.colorNickNames) {
17955       if( c == appData.colorNickNames[0] ) c = 'w'; else
17956       if( c == appData.colorNickNames[1] ) c = 'b';
17957     }
17958     switch (c) {
17959       case 'w':
17960         *blackPlaysFirst = FALSE;
17961         break;
17962       case 'b':
17963         *blackPlaysFirst = TRUE;
17964         break;
17965       default:
17966         return FALSE;
17967     }
17968
17969     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17970     /* return the extra info in global variiables             */
17971
17972     /* set defaults in case FEN is incomplete */
17973     board[EP_STATUS] = EP_UNKNOWN;
17974     for(i=0; i<nrCastlingRights; i++ ) {
17975         board[CASTLING][i] =
17976             appData.fischerCastling ? NoRights : initialRights[i];
17977     }   /* assume possible unless obviously impossible */
17978     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17979     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17980     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17981                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17982     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17983     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17984     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17985                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17986     FENrulePlies = 0;
17987
17988     while(*p==' ') p++;
17989     if(nrCastlingRights) {
17990       int fischer = 0;
17991       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17992       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17993           /* castling indicator present, so default becomes no castlings */
17994           for(i=0; i<nrCastlingRights; i++ ) {
17995                  board[CASTLING][i] = NoRights;
17996           }
17997       }
17998       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17999              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18000              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18001              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18002         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18003
18004         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18005             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18006             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18007         }
18008         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18009             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18010         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18011                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18012         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18013                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18014         switch(c) {
18015           case'K':
18016               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18017               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18018               board[CASTLING][2] = whiteKingFile;
18019               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18020               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18021               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18022               break;
18023           case'Q':
18024               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18025               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18026               board[CASTLING][2] = whiteKingFile;
18027               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18028               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18029               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18030               break;
18031           case'k':
18032               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18033               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18034               board[CASTLING][5] = blackKingFile;
18035               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18036               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18037               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18038               break;
18039           case'q':
18040               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18041               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18042               board[CASTLING][5] = blackKingFile;
18043               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18044               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18045               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18046           case '-':
18047               break;
18048           default: /* FRC castlings */
18049               if(c >= 'a') { /* black rights */
18050                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18051                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18052                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18053                   if(i == BOARD_RGHT) break;
18054                   board[CASTLING][5] = i;
18055                   c -= AAA;
18056                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18057                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18058                   if(c > i)
18059                       board[CASTLING][3] = c;
18060                   else
18061                       board[CASTLING][4] = c;
18062               } else { /* white rights */
18063                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18064                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18065                     if(board[0][i] == WhiteKing) break;
18066                   if(i == BOARD_RGHT) break;
18067                   board[CASTLING][2] = i;
18068                   c -= AAA - 'a' + 'A';
18069                   if(board[0][c] >= WhiteKing) break;
18070                   if(c > i)
18071                       board[CASTLING][0] = c;
18072                   else
18073                       board[CASTLING][1] = c;
18074               }
18075         }
18076       }
18077       for(i=0; i<nrCastlingRights; i++)
18078         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18079       if(gameInfo.variant == VariantSChess)
18080         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18081       if(fischer && shuffle) appData.fischerCastling = TRUE;
18082     if (appData.debugMode) {
18083         fprintf(debugFP, "FEN castling rights:");
18084         for(i=0; i<nrCastlingRights; i++)
18085         fprintf(debugFP, " %d", board[CASTLING][i]);
18086         fprintf(debugFP, "\n");
18087     }
18088
18089       while(*p==' ') p++;
18090     }
18091
18092     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18093
18094     /* read e.p. field in games that know e.p. capture */
18095     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18096        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18097        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18098       if(*p=='-') {
18099         p++; board[EP_STATUS] = EP_NONE;
18100       } else {
18101          char c = *p++ - AAA;
18102
18103          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18104          if(*p >= '0' && *p <='9') p++;
18105          board[EP_STATUS] = c;
18106       }
18107     }
18108
18109
18110     if(sscanf(p, "%d", &i) == 1) {
18111         FENrulePlies = i; /* 50-move ply counter */
18112         /* (The move number is still ignored)    */
18113     }
18114
18115     return TRUE;
18116 }
18117
18118 void
18119 EditPositionPasteFEN (char *fen)
18120 {
18121   if (fen != NULL) {
18122     Board initial_position;
18123
18124     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18125       DisplayError(_("Bad FEN position in clipboard"), 0);
18126       return ;
18127     } else {
18128       int savedBlackPlaysFirst = blackPlaysFirst;
18129       EditPositionEvent();
18130       blackPlaysFirst = savedBlackPlaysFirst;
18131       CopyBoard(boards[0], initial_position);
18132       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18133       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18134       DisplayBothClocks();
18135       DrawPosition(FALSE, boards[currentMove]);
18136     }
18137   }
18138 }
18139
18140 static char cseq[12] = "\\   ";
18141
18142 Boolean
18143 set_cont_sequence (char *new_seq)
18144 {
18145     int len;
18146     Boolean ret;
18147
18148     // handle bad attempts to set the sequence
18149         if (!new_seq)
18150                 return 0; // acceptable error - no debug
18151
18152     len = strlen(new_seq);
18153     ret = (len > 0) && (len < sizeof(cseq));
18154     if (ret)
18155       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18156     else if (appData.debugMode)
18157       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18158     return ret;
18159 }
18160
18161 /*
18162     reformat a source message so words don't cross the width boundary.  internal
18163     newlines are not removed.  returns the wrapped size (no null character unless
18164     included in source message).  If dest is NULL, only calculate the size required
18165     for the dest buffer.  lp argument indicats line position upon entry, and it's
18166     passed back upon exit.
18167 */
18168 int
18169 wrap (char *dest, char *src, int count, int width, int *lp)
18170 {
18171     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18172
18173     cseq_len = strlen(cseq);
18174     old_line = line = *lp;
18175     ansi = len = clen = 0;
18176
18177     for (i=0; i < count; i++)
18178     {
18179         if (src[i] == '\033')
18180             ansi = 1;
18181
18182         // if we hit the width, back up
18183         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18184         {
18185             // store i & len in case the word is too long
18186             old_i = i, old_len = len;
18187
18188             // find the end of the last word
18189             while (i && src[i] != ' ' && src[i] != '\n')
18190             {
18191                 i--;
18192                 len--;
18193             }
18194
18195             // word too long?  restore i & len before splitting it
18196             if ((old_i-i+clen) >= width)
18197             {
18198                 i = old_i;
18199                 len = old_len;
18200             }
18201
18202             // extra space?
18203             if (i && src[i-1] == ' ')
18204                 len--;
18205
18206             if (src[i] != ' ' && src[i] != '\n')
18207             {
18208                 i--;
18209                 if (len)
18210                     len--;
18211             }
18212
18213             // now append the newline and continuation sequence
18214             if (dest)
18215                 dest[len] = '\n';
18216             len++;
18217             if (dest)
18218                 strncpy(dest+len, cseq, cseq_len);
18219             len += cseq_len;
18220             line = cseq_len;
18221             clen = cseq_len;
18222             continue;
18223         }
18224
18225         if (dest)
18226             dest[len] = src[i];
18227         len++;
18228         if (!ansi)
18229             line++;
18230         if (src[i] == '\n')
18231             line = 0;
18232         if (src[i] == 'm')
18233             ansi = 0;
18234     }
18235     if (dest && appData.debugMode)
18236     {
18237         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18238             count, width, line, len, *lp);
18239         show_bytes(debugFP, src, count);
18240         fprintf(debugFP, "\ndest: ");
18241         show_bytes(debugFP, dest, len);
18242         fprintf(debugFP, "\n");
18243     }
18244     *lp = dest ? line : old_line;
18245
18246     return len;
18247 }
18248
18249 // [HGM] vari: routines for shelving variations
18250 Boolean modeRestore = FALSE;
18251
18252 void
18253 PushInner (int firstMove, int lastMove)
18254 {
18255         int i, j, nrMoves = lastMove - firstMove;
18256
18257         // push current tail of game on stack
18258         savedResult[storedGames] = gameInfo.result;
18259         savedDetails[storedGames] = gameInfo.resultDetails;
18260         gameInfo.resultDetails = NULL;
18261         savedFirst[storedGames] = firstMove;
18262         savedLast [storedGames] = lastMove;
18263         savedFramePtr[storedGames] = framePtr;
18264         framePtr -= nrMoves; // reserve space for the boards
18265         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18266             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18267             for(j=0; j<MOVE_LEN; j++)
18268                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18269             for(j=0; j<2*MOVE_LEN; j++)
18270                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18271             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18272             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18273             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18274             pvInfoList[firstMove+i-1].depth = 0;
18275             commentList[framePtr+i] = commentList[firstMove+i];
18276             commentList[firstMove+i] = NULL;
18277         }
18278
18279         storedGames++;
18280         forwardMostMove = firstMove; // truncate game so we can start variation
18281 }
18282
18283 void
18284 PushTail (int firstMove, int lastMove)
18285 {
18286         if(appData.icsActive) { // only in local mode
18287                 forwardMostMove = currentMove; // mimic old ICS behavior
18288                 return;
18289         }
18290         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18291
18292         PushInner(firstMove, lastMove);
18293         if(storedGames == 1) GreyRevert(FALSE);
18294         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18295 }
18296
18297 void
18298 PopInner (Boolean annotate)
18299 {
18300         int i, j, nrMoves;
18301         char buf[8000], moveBuf[20];
18302
18303         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18304         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18305         nrMoves = savedLast[storedGames] - currentMove;
18306         if(annotate) {
18307                 int cnt = 10;
18308                 if(!WhiteOnMove(currentMove))
18309                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18310                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18311                 for(i=currentMove; i<forwardMostMove; i++) {
18312                         if(WhiteOnMove(i))
18313                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18314                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18315                         strcat(buf, moveBuf);
18316                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18317                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18318                 }
18319                 strcat(buf, ")");
18320         }
18321         for(i=1; i<=nrMoves; i++) { // copy last variation back
18322             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18323             for(j=0; j<MOVE_LEN; j++)
18324                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18325             for(j=0; j<2*MOVE_LEN; j++)
18326                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18327             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18328             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18329             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18330             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18331             commentList[currentMove+i] = commentList[framePtr+i];
18332             commentList[framePtr+i] = NULL;
18333         }
18334         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18335         framePtr = savedFramePtr[storedGames];
18336         gameInfo.result = savedResult[storedGames];
18337         if(gameInfo.resultDetails != NULL) {
18338             free(gameInfo.resultDetails);
18339       }
18340         gameInfo.resultDetails = savedDetails[storedGames];
18341         forwardMostMove = currentMove + nrMoves;
18342 }
18343
18344 Boolean
18345 PopTail (Boolean annotate)
18346 {
18347         if(appData.icsActive) return FALSE; // only in local mode
18348         if(!storedGames) return FALSE; // sanity
18349         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18350
18351         PopInner(annotate);
18352         if(currentMove < forwardMostMove) ForwardEvent(); else
18353         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18354
18355         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18356         return TRUE;
18357 }
18358
18359 void
18360 CleanupTail ()
18361 {       // remove all shelved variations
18362         int i;
18363         for(i=0; i<storedGames; i++) {
18364             if(savedDetails[i])
18365                 free(savedDetails[i]);
18366             savedDetails[i] = NULL;
18367         }
18368         for(i=framePtr; i<MAX_MOVES; i++) {
18369                 if(commentList[i]) free(commentList[i]);
18370                 commentList[i] = NULL;
18371         }
18372         framePtr = MAX_MOVES-1;
18373         storedGames = 0;
18374 }
18375
18376 void
18377 LoadVariation (int index, char *text)
18378 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18379         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18380         int level = 0, move;
18381
18382         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18383         // first find outermost bracketing variation
18384         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18385             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18386                 if(*p == '{') wait = '}'; else
18387                 if(*p == '[') wait = ']'; else
18388                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18389                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18390             }
18391             if(*p == wait) wait = NULLCHAR; // closing ]} found
18392             p++;
18393         }
18394         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18395         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18396         end[1] = NULLCHAR; // clip off comment beyond variation
18397         ToNrEvent(currentMove-1);
18398         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18399         // kludge: use ParsePV() to append variation to game
18400         move = currentMove;
18401         ParsePV(start, TRUE, TRUE);
18402         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18403         ClearPremoveHighlights();
18404         CommentPopDown();
18405         ToNrEvent(currentMove+1);
18406 }
18407
18408 void
18409 LoadTheme ()
18410 {
18411     char *p, *q, buf[MSG_SIZ];
18412     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18413         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18414         ParseArgsFromString(buf);
18415         ActivateTheme(TRUE); // also redo colors
18416         return;
18417     }
18418     p = nickName;
18419     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18420     {
18421         int len;
18422         q = appData.themeNames;
18423         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18424       if(appData.useBitmaps) {
18425         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18426                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18427                 appData.liteBackTextureMode,
18428                 appData.darkBackTextureMode );
18429       } else {
18430         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18431                 Col2Text(2),   // lightSquareColor
18432                 Col2Text(3) ); // darkSquareColor
18433       }
18434       if(appData.useBorder) {
18435         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18436                 appData.border);
18437       } else {
18438         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18439       }
18440       if(appData.useFont) {
18441         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18442                 appData.renderPiecesWithFont,
18443                 appData.fontToPieceTable,
18444                 Col2Text(9),    // appData.fontBackColorWhite
18445                 Col2Text(10) ); // appData.fontForeColorBlack
18446       } else {
18447         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18448                 appData.pieceDirectory);
18449         if(!appData.pieceDirectory[0])
18450           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18451                 Col2Text(0),   // whitePieceColor
18452                 Col2Text(1) ); // blackPieceColor
18453       }
18454       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18455                 Col2Text(4),   // highlightSquareColor
18456                 Col2Text(5) ); // premoveHighlightColor
18457         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18458         if(insert != q) insert[-1] = NULLCHAR;
18459         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18460         if(q)   free(q);
18461     }
18462     ActivateTheme(FALSE);
18463 }