39467aeae6f8fc4ff54ea1497e36755ca6a25300
[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
851     /* New features added by Tord: */
852     cps->useFEN960 = FALSE;
853     cps->useOOCastle = TRUE;
854     /* End of new features added by Tord. */
855     cps->fenOverride  = appData.fenOverride[n];
856
857     /* [HGM] time odds: set factor for each machine */
858     cps->timeOdds  = appData.timeOdds[n];
859
860     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
861     cps->accumulateTC = appData.accumulateTC[n];
862     cps->maxNrOfSessions = 1;
863
864     /* [HGM] debug */
865     cps->debug = FALSE;
866
867     cps->drawDepth = appData.drawDepth[n];
868     cps->supportsNPS = UNKNOWN;
869     cps->memSize = FALSE;
870     cps->maxCores = FALSE;
871     ASSIGN(cps->egtFormats, "");
872
873     /* [HGM] options */
874     cps->optionSettings  = appData.engOptions[n];
875
876     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
877     cps->isUCI = appData.isUCI[n]; /* [AS] */
878     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
879     cps->highlight = 0;
880
881     if (appData.protocolVersion[n] > PROTOVER
882         || appData.protocolVersion[n] < 1)
883       {
884         char buf[MSG_SIZ];
885         int len;
886
887         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
888                        appData.protocolVersion[n]);
889         if( (len >= MSG_SIZ) && appData.debugMode )
890           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
891
892         DisplayFatalError(buf, 0, 2);
893       }
894     else
895       {
896         cps->protocolVersion = appData.protocolVersion[n];
897       }
898
899     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
900     ParseFeatures(appData.featureDefaults, cps);
901 }
902
903 ChessProgramState *savCps;
904
905 GameMode oldMode;
906
907 void
908 LoadEngine ()
909 {
910     int i;
911     if(WaitForEngine(savCps, LoadEngine)) return;
912     CommonEngineInit(); // recalculate time odds
913     if(gameInfo.variant != StringToVariant(appData.variant)) {
914         // we changed variant when loading the engine; this forces us to reset
915         Reset(TRUE, savCps != &first);
916         oldMode = BeginningOfGame; // to prevent restoring old mode
917     }
918     InitChessProgram(savCps, FALSE);
919     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
920     DisplayMessage("", "");
921     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
922     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
923     ThawUI();
924     SetGNUMode();
925     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
926 }
927
928 void
929 ReplaceEngine (ChessProgramState *cps, int n)
930 {
931     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
932     keepInfo = 1;
933     if(oldMode != BeginningOfGame) EditGameEvent();
934     keepInfo = 0;
935     UnloadEngine(cps);
936     appData.noChessProgram = FALSE;
937     appData.clockMode = TRUE;
938     InitEngine(cps, n);
939     UpdateLogos(TRUE);
940     if(n) return; // only startup first engine immediately; second can wait
941     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
942     LoadEngine();
943 }
944
945 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
946 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
947
948 static char resetOptions[] =
949         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
950         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
951         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
952         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
953
954 void
955 FloatToFront(char **list, char *engineLine)
956 {
957     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
958     int i=0;
959     if(appData.recentEngines <= 0) return;
960     TidyProgramName(engineLine, "localhost", tidy+1);
961     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
962     strncpy(buf+1, *list, MSG_SIZ-50);
963     if(p = strstr(buf, tidy)) { // tidy name appears in list
964         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
965         while(*p++ = *++q); // squeeze out
966     }
967     strcat(tidy, buf+1); // put list behind tidy name
968     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
969     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
970     ASSIGN(*list, tidy+1);
971 }
972
973 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
974
975 void
976 Load (ChessProgramState *cps, int i)
977 {
978     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
979     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
980         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
981         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
982         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
983         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
984         appData.firstProtocolVersion = PROTOVER;
985         ParseArgsFromString(buf);
986         SwapEngines(i);
987         ReplaceEngine(cps, i);
988         FloatToFront(&appData.recentEngineList, engineLine);
989         return;
990     }
991     p = engineName;
992     while(q = strchr(p, SLASH)) p = q+1;
993     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
994     if(engineDir[0] != NULLCHAR) {
995         ASSIGN(appData.directory[i], engineDir); p = engineName;
996     } else if(p != engineName) { // derive directory from engine path, when not given
997         p[-1] = 0;
998         ASSIGN(appData.directory[i], engineName);
999         p[-1] = SLASH;
1000         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1001     } else { ASSIGN(appData.directory[i], "."); }
1002     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1003     if(params[0]) {
1004         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1005         snprintf(command, MSG_SIZ, "%s %s", p, params);
1006         p = command;
1007     }
1008     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1009     ASSIGN(appData.chessProgram[i], p);
1010     appData.isUCI[i] = isUCI;
1011     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1012     appData.hasOwnBookUCI[i] = hasBook;
1013     if(!nickName[0]) useNick = FALSE;
1014     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1015     if(addToList) {
1016         int len;
1017         char quote;
1018         q = firstChessProgramNames;
1019         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1020         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1021         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1022                         quote, p, quote, appData.directory[i],
1023                         useNick ? " -fn \"" : "",
1024                         useNick ? nickName : "",
1025                         useNick ? "\"" : "",
1026                         v1 ? " -firstProtocolVersion 1" : "",
1027                         hasBook ? "" : " -fNoOwnBookUCI",
1028                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1029                         storeVariant ? " -variant " : "",
1030                         storeVariant ? VariantName(gameInfo.variant) : "");
1031         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1032         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1033         if(insert != q) insert[-1] = NULLCHAR;
1034         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1035         if(q)   free(q);
1036         FloatToFront(&appData.recentEngineList, buf);
1037     }
1038     ReplaceEngine(cps, i);
1039 }
1040
1041 void
1042 InitTimeControls ()
1043 {
1044     int matched, min, sec;
1045     /*
1046      * Parse timeControl resource
1047      */
1048     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1049                           appData.movesPerSession)) {
1050         char buf[MSG_SIZ];
1051         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1052         DisplayFatalError(buf, 0, 2);
1053     }
1054
1055     /*
1056      * Parse searchTime resource
1057      */
1058     if (*appData.searchTime != NULLCHAR) {
1059         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1060         if (matched == 1) {
1061             searchTime = min * 60;
1062         } else if (matched == 2) {
1063             searchTime = min * 60 + sec;
1064         } else {
1065             char buf[MSG_SIZ];
1066             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1067             DisplayFatalError(buf, 0, 2);
1068         }
1069     }
1070 }
1071
1072 void
1073 InitBackEnd1 ()
1074 {
1075
1076     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1077     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1078
1079     GetTimeMark(&programStartTime);
1080     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1081     appData.seedBase = random() + (random()<<15);
1082     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1083
1084     ClearProgramStats();
1085     programStats.ok_to_send = 1;
1086     programStats.seen_stat = 0;
1087
1088     /*
1089      * Initialize game list
1090      */
1091     ListNew(&gameList);
1092
1093
1094     /*
1095      * Internet chess server status
1096      */
1097     if (appData.icsActive) {
1098         appData.matchMode = FALSE;
1099         appData.matchGames = 0;
1100 #if ZIPPY
1101         appData.noChessProgram = !appData.zippyPlay;
1102 #else
1103         appData.zippyPlay = FALSE;
1104         appData.zippyTalk = FALSE;
1105         appData.noChessProgram = TRUE;
1106 #endif
1107         if (*appData.icsHelper != NULLCHAR) {
1108             appData.useTelnet = TRUE;
1109             appData.telnetProgram = appData.icsHelper;
1110         }
1111     } else {
1112         appData.zippyTalk = appData.zippyPlay = FALSE;
1113     }
1114
1115     /* [AS] Initialize pv info list [HGM] and game state */
1116     {
1117         int i, j;
1118
1119         for( i=0; i<=framePtr; i++ ) {
1120             pvInfoList[i].depth = -1;
1121             boards[i][EP_STATUS] = EP_NONE;
1122             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1123         }
1124     }
1125
1126     InitTimeControls();
1127
1128     /* [AS] Adjudication threshold */
1129     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1130
1131     InitEngine(&first, 0);
1132     InitEngine(&second, 1);
1133     CommonEngineInit();
1134
1135     pairing.which = "pairing"; // pairing engine
1136     pairing.pr = NoProc;
1137     pairing.isr = NULL;
1138     pairing.program = appData.pairingEngine;
1139     pairing.host = "localhost";
1140     pairing.dir = ".";
1141
1142     if (appData.icsActive) {
1143         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1144     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1145         appData.clockMode = FALSE;
1146         first.sendTime = second.sendTime = 0;
1147     }
1148
1149 #if ZIPPY
1150     /* Override some settings from environment variables, for backward
1151        compatibility.  Unfortunately it's not feasible to have the env
1152        vars just set defaults, at least in xboard.  Ugh.
1153     */
1154     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1155       ZippyInit();
1156     }
1157 #endif
1158
1159     if (!appData.icsActive) {
1160       char buf[MSG_SIZ];
1161       int len;
1162
1163       /* Check for variants that are supported only in ICS mode,
1164          or not at all.  Some that are accepted here nevertheless
1165          have bugs; see comments below.
1166       */
1167       VariantClass variant = StringToVariant(appData.variant);
1168       switch (variant) {
1169       case VariantBughouse:     /* need four players and two boards */
1170       case VariantKriegspiel:   /* need to hide pieces and move details */
1171         /* case VariantFischeRandom: (Fabien: moved below) */
1172         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1173         if( (len >= MSG_SIZ) && appData.debugMode )
1174           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1175
1176         DisplayFatalError(buf, 0, 2);
1177         return;
1178
1179       case VariantUnknown:
1180       case VariantLoadable:
1181       case Variant29:
1182       case Variant30:
1183       case Variant31:
1184       case Variant32:
1185       case Variant33:
1186       case Variant34:
1187       case Variant35:
1188       case Variant36:
1189       default:
1190         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1191         if( (len >= MSG_SIZ) && appData.debugMode )
1192           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1193
1194         DisplayFatalError(buf, 0, 2);
1195         return;
1196
1197       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1198       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1199       case VariantGothic:     /* [HGM] should work */
1200       case VariantCapablanca: /* [HGM] should work */
1201       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1202       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1203       case VariantChu:        /* [HGM] experimental */
1204       case VariantKnightmate: /* [HGM] should work */
1205       case VariantCylinder:   /* [HGM] untested */
1206       case VariantFalcon:     /* [HGM] untested */
1207       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1208                                  offboard interposition not understood */
1209       case VariantNormal:     /* definitely works! */
1210       case VariantWildCastle: /* pieces not automatically shuffled */
1211       case VariantNoCastle:   /* pieces not automatically shuffled */
1212       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1213       case VariantLosers:     /* should work except for win condition,
1214                                  and doesn't know captures are mandatory */
1215       case VariantSuicide:    /* should work except for win condition,
1216                                  and doesn't know captures are mandatory */
1217       case VariantGiveaway:   /* should work except for win condition,
1218                                  and doesn't know captures are mandatory */
1219       case VariantTwoKings:   /* should work */
1220       case VariantAtomic:     /* should work except for win condition */
1221       case Variant3Check:     /* should work except for win condition */
1222       case VariantShatranj:   /* should work except for all win conditions */
1223       case VariantMakruk:     /* should work except for draw countdown */
1224       case VariantASEAN :     /* should work except for draw countdown */
1225       case VariantBerolina:   /* might work if TestLegality is off */
1226       case VariantCapaRandom: /* should work */
1227       case VariantJanus:      /* should work */
1228       case VariantSuper:      /* experimental */
1229       case VariantGreat:      /* experimental, requires legality testing to be off */
1230       case VariantSChess:     /* S-Chess, should work */
1231       case VariantGrand:      /* should work */
1232       case VariantSpartan:    /* should work */
1233       case VariantLion:       /* should work */
1234       case VariantChuChess:   /* should work */
1235         break;
1236       }
1237     }
1238
1239 }
1240
1241 int
1242 NextIntegerFromString (char ** str, long * value)
1243 {
1244     int result = -1;
1245     char * s = *str;
1246
1247     while( *s == ' ' || *s == '\t' ) {
1248         s++;
1249     }
1250
1251     *value = 0;
1252
1253     if( *s >= '0' && *s <= '9' ) {
1254         while( *s >= '0' && *s <= '9' ) {
1255             *value = *value * 10 + (*s - '0');
1256             s++;
1257         }
1258
1259         result = 0;
1260     }
1261
1262     *str = s;
1263
1264     return result;
1265 }
1266
1267 int
1268 NextTimeControlFromString (char ** str, long * value)
1269 {
1270     long temp;
1271     int result = NextIntegerFromString( str, &temp );
1272
1273     if( result == 0 ) {
1274         *value = temp * 60; /* Minutes */
1275         if( **str == ':' ) {
1276             (*str)++;
1277             result = NextIntegerFromString( str, &temp );
1278             *value += temp; /* Seconds */
1279         }
1280     }
1281
1282     return result;
1283 }
1284
1285 int
1286 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1287 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1288     int result = -1, type = 0; long temp, temp2;
1289
1290     if(**str != ':') return -1; // old params remain in force!
1291     (*str)++;
1292     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1293     if( NextIntegerFromString( str, &temp ) ) return -1;
1294     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1295
1296     if(**str != '/') {
1297         /* time only: incremental or sudden-death time control */
1298         if(**str == '+') { /* increment follows; read it */
1299             (*str)++;
1300             if(**str == '!') type = *(*str)++; // Bronstein TC
1301             if(result = NextIntegerFromString( str, &temp2)) return -1;
1302             *inc = temp2 * 1000;
1303             if(**str == '.') { // read fraction of increment
1304                 char *start = ++(*str);
1305                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1306                 temp2 *= 1000;
1307                 while(start++ < *str) temp2 /= 10;
1308                 *inc += temp2;
1309             }
1310         } else *inc = 0;
1311         *moves = 0; *tc = temp * 1000; *incType = type;
1312         return 0;
1313     }
1314
1315     (*str)++; /* classical time control */
1316     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1317
1318     if(result == 0) {
1319         *moves = temp;
1320         *tc    = temp2 * 1000;
1321         *inc   = 0;
1322         *incType = type;
1323     }
1324     return result;
1325 }
1326
1327 int
1328 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1329 {   /* [HGM] get time to add from the multi-session time-control string */
1330     int incType, moves=1; /* kludge to force reading of first session */
1331     long time, increment;
1332     char *s = tcString;
1333
1334     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1335     do {
1336         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1337         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1338         if(movenr == -1) return time;    /* last move before new session     */
1339         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1340         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1341         if(!moves) return increment;     /* current session is incremental   */
1342         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1343     } while(movenr >= -1);               /* try again for next session       */
1344
1345     return 0; // no new time quota on this move
1346 }
1347
1348 int
1349 ParseTimeControl (char *tc, float ti, int mps)
1350 {
1351   long tc1;
1352   long tc2;
1353   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1354   int min, sec=0;
1355
1356   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1357   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1358       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1359   if(ti > 0) {
1360
1361     if(mps)
1362       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1363     else
1364       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1365   } else {
1366     if(mps)
1367       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1368     else
1369       snprintf(buf, MSG_SIZ, ":%s", mytc);
1370   }
1371   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1372
1373   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1374     return FALSE;
1375   }
1376
1377   if( *tc == '/' ) {
1378     /* Parse second time control */
1379     tc++;
1380
1381     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1382       return FALSE;
1383     }
1384
1385     if( tc2 == 0 ) {
1386       return FALSE;
1387     }
1388
1389     timeControl_2 = tc2 * 1000;
1390   }
1391   else {
1392     timeControl_2 = 0;
1393   }
1394
1395   if( tc1 == 0 ) {
1396     return FALSE;
1397   }
1398
1399   timeControl = tc1 * 1000;
1400
1401   if (ti >= 0) {
1402     timeIncrement = ti * 1000;  /* convert to ms */
1403     movesPerSession = 0;
1404   } else {
1405     timeIncrement = 0;
1406     movesPerSession = mps;
1407   }
1408   return TRUE;
1409 }
1410
1411 void
1412 InitBackEnd2 ()
1413 {
1414     if (appData.debugMode) {
1415 #    ifdef __GIT_VERSION
1416       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1417 #    else
1418       fprintf(debugFP, "Version: %s\n", programVersion);
1419 #    endif
1420     }
1421     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1422
1423     set_cont_sequence(appData.wrapContSeq);
1424     if (appData.matchGames > 0) {
1425         appData.matchMode = TRUE;
1426     } else if (appData.matchMode) {
1427         appData.matchGames = 1;
1428     }
1429     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1430         appData.matchGames = appData.sameColorGames;
1431     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1432         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1433         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1434     }
1435     Reset(TRUE, FALSE);
1436     if (appData.noChessProgram || first.protocolVersion == 1) {
1437       InitBackEnd3();
1438     } else {
1439       /* kludge: allow timeout for initial "feature" commands */
1440       FreezeUI();
1441       DisplayMessage("", _("Starting chess program"));
1442       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1443     }
1444 }
1445
1446 int
1447 CalculateIndex (int index, int gameNr)
1448 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1449     int res;
1450     if(index > 0) return index; // fixed nmber
1451     if(index == 0) return 1;
1452     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1453     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1454     return res;
1455 }
1456
1457 int
1458 LoadGameOrPosition (int gameNr)
1459 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1460     if (*appData.loadGameFile != NULLCHAR) {
1461         if (!LoadGameFromFile(appData.loadGameFile,
1462                 CalculateIndex(appData.loadGameIndex, gameNr),
1463                               appData.loadGameFile, FALSE)) {
1464             DisplayFatalError(_("Bad game file"), 0, 1);
1465             return 0;
1466         }
1467     } else if (*appData.loadPositionFile != NULLCHAR) {
1468         if (!LoadPositionFromFile(appData.loadPositionFile,
1469                 CalculateIndex(appData.loadPositionIndex, gameNr),
1470                                   appData.loadPositionFile)) {
1471             DisplayFatalError(_("Bad position file"), 0, 1);
1472             return 0;
1473         }
1474     }
1475     return 1;
1476 }
1477
1478 void
1479 ReserveGame (int gameNr, char resChar)
1480 {
1481     FILE *tf = fopen(appData.tourneyFile, "r+");
1482     char *p, *q, c, buf[MSG_SIZ];
1483     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1484     safeStrCpy(buf, lastMsg, MSG_SIZ);
1485     DisplayMessage(_("Pick new game"), "");
1486     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1487     ParseArgsFromFile(tf);
1488     p = q = appData.results;
1489     if(appData.debugMode) {
1490       char *r = appData.participants;
1491       fprintf(debugFP, "results = '%s'\n", p);
1492       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1493       fprintf(debugFP, "\n");
1494     }
1495     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1496     nextGame = q - p;
1497     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1498     safeStrCpy(q, p, strlen(p) + 2);
1499     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1500     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1501     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1502         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1503         q[nextGame] = '*';
1504     }
1505     fseek(tf, -(strlen(p)+4), SEEK_END);
1506     c = fgetc(tf);
1507     if(c != '"') // depending on DOS or Unix line endings we can be one off
1508          fseek(tf, -(strlen(p)+2), SEEK_END);
1509     else fseek(tf, -(strlen(p)+3), SEEK_END);
1510     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1511     DisplayMessage(buf, "");
1512     free(p); appData.results = q;
1513     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1514        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1515       int round = appData.defaultMatchGames * appData.tourneyType;
1516       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1517          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1518         UnloadEngine(&first);  // next game belongs to other pairing;
1519         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1520     }
1521     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1522 }
1523
1524 void
1525 MatchEvent (int mode)
1526 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1527         int dummy;
1528         if(matchMode) { // already in match mode: switch it off
1529             abortMatch = TRUE;
1530             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1531             return;
1532         }
1533 //      if(gameMode != BeginningOfGame) {
1534 //          DisplayError(_("You can only start a match from the initial position."), 0);
1535 //          return;
1536 //      }
1537         abortMatch = FALSE;
1538         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1539         /* Set up machine vs. machine match */
1540         nextGame = 0;
1541         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1542         if(appData.tourneyFile[0]) {
1543             ReserveGame(-1, 0);
1544             if(nextGame > appData.matchGames) {
1545                 char buf[MSG_SIZ];
1546                 if(strchr(appData.results, '*') == NULL) {
1547                     FILE *f;
1548                     appData.tourneyCycles++;
1549                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1550                         fclose(f);
1551                         NextTourneyGame(-1, &dummy);
1552                         ReserveGame(-1, 0);
1553                         if(nextGame <= appData.matchGames) {
1554                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1555                             matchMode = mode;
1556                             ScheduleDelayedEvent(NextMatchGame, 10000);
1557                             return;
1558                         }
1559                     }
1560                 }
1561                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1562                 DisplayError(buf, 0);
1563                 appData.tourneyFile[0] = 0;
1564                 return;
1565             }
1566         } else
1567         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1568             DisplayFatalError(_("Can't have a match with no chess programs"),
1569                               0, 2);
1570             return;
1571         }
1572         matchMode = mode;
1573         matchGame = roundNr = 1;
1574         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1575         NextMatchGame();
1576 }
1577
1578 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1579
1580 void
1581 InitBackEnd3 P((void))
1582 {
1583     GameMode initialMode;
1584     char buf[MSG_SIZ];
1585     int err, len;
1586
1587     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1588        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1589         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1590        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1591        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1592         char c, *q = first.variants, *p = strchr(q, ',');
1593         if(p) *p = NULLCHAR;
1594         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1595             int w, h, s;
1596             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1597                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1598             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1599             Reset(TRUE, FALSE);         // and re-initialize
1600         }
1601         if(p) *p = ',';
1602     }
1603
1604     InitChessProgram(&first, startedFromSetupPosition);
1605
1606     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1607         free(programVersion);
1608         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1609         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1610         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1611     }
1612
1613     if (appData.icsActive) {
1614 #ifdef WIN32
1615         /* [DM] Make a console window if needed [HGM] merged ifs */
1616         ConsoleCreate();
1617 #endif
1618         err = establish();
1619         if (err != 0)
1620           {
1621             if (*appData.icsCommPort != NULLCHAR)
1622               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1623                              appData.icsCommPort);
1624             else
1625               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1626                         appData.icsHost, appData.icsPort);
1627
1628             if( (len >= MSG_SIZ) && appData.debugMode )
1629               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631             DisplayFatalError(buf, err, 1);
1632             return;
1633         }
1634         SetICSMode();
1635         telnetISR =
1636           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1637         fromUserISR =
1638           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1639         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1640             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1641     } else if (appData.noChessProgram) {
1642         SetNCPMode();
1643     } else {
1644         SetGNUMode();
1645     }
1646
1647     if (*appData.cmailGameName != NULLCHAR) {
1648         SetCmailMode();
1649         OpenLoopback(&cmailPR);
1650         cmailISR =
1651           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1652     }
1653
1654     ThawUI();
1655     DisplayMessage("", "");
1656     if (StrCaseCmp(appData.initialMode, "") == 0) {
1657       initialMode = BeginningOfGame;
1658       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1659         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1660         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1661         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1662         ModeHighlight();
1663       }
1664     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1665       initialMode = TwoMachinesPlay;
1666     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1667       initialMode = AnalyzeFile;
1668     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1669       initialMode = AnalyzeMode;
1670     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1671       initialMode = MachinePlaysWhite;
1672     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1673       initialMode = MachinePlaysBlack;
1674     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1675       initialMode = EditGame;
1676     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1677       initialMode = EditPosition;
1678     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1679       initialMode = Training;
1680     } else {
1681       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1682       if( (len >= MSG_SIZ) && appData.debugMode )
1683         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1684
1685       DisplayFatalError(buf, 0, 2);
1686       return;
1687     }
1688
1689     if (appData.matchMode) {
1690         if(appData.tourneyFile[0]) { // start tourney from command line
1691             FILE *f;
1692             if(f = fopen(appData.tourneyFile, "r")) {
1693                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1694                 fclose(f);
1695                 appData.clockMode = TRUE;
1696                 SetGNUMode();
1697             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1698         }
1699         MatchEvent(TRUE);
1700     } else if (*appData.cmailGameName != NULLCHAR) {
1701         /* Set up cmail mode */
1702         ReloadCmailMsgEvent(TRUE);
1703     } else {
1704         /* Set up other modes */
1705         if (initialMode == AnalyzeFile) {
1706           if (*appData.loadGameFile == NULLCHAR) {
1707             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1708             return;
1709           }
1710         }
1711         if (*appData.loadGameFile != NULLCHAR) {
1712             (void) LoadGameFromFile(appData.loadGameFile,
1713                                     appData.loadGameIndex,
1714                                     appData.loadGameFile, TRUE);
1715         } else if (*appData.loadPositionFile != NULLCHAR) {
1716             (void) LoadPositionFromFile(appData.loadPositionFile,
1717                                         appData.loadPositionIndex,
1718                                         appData.loadPositionFile);
1719             /* [HGM] try to make self-starting even after FEN load */
1720             /* to allow automatic setup of fairy variants with wtm */
1721             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1722                 gameMode = BeginningOfGame;
1723                 setboardSpoiledMachineBlack = 1;
1724             }
1725             /* [HGM] loadPos: make that every new game uses the setup */
1726             /* from file as long as we do not switch variant          */
1727             if(!blackPlaysFirst) {
1728                 startedFromPositionFile = TRUE;
1729                 CopyBoard(filePosition, boards[0]);
1730             }
1731         }
1732         if (initialMode == AnalyzeMode) {
1733           if (appData.noChessProgram) {
1734             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1735             return;
1736           }
1737           if (appData.icsActive) {
1738             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1739             return;
1740           }
1741           AnalyzeModeEvent();
1742         } else if (initialMode == AnalyzeFile) {
1743           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1744           ShowThinkingEvent();
1745           AnalyzeFileEvent();
1746           AnalysisPeriodicEvent(1);
1747         } else if (initialMode == MachinePlaysWhite) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1750                               0, 2);
1751             return;
1752           }
1753           if (appData.icsActive) {
1754             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1755                               0, 2);
1756             return;
1757           }
1758           MachineWhiteEvent();
1759         } else if (initialMode == MachinePlaysBlack) {
1760           if (appData.noChessProgram) {
1761             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1762                               0, 2);
1763             return;
1764           }
1765           if (appData.icsActive) {
1766             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1767                               0, 2);
1768             return;
1769           }
1770           MachineBlackEvent();
1771         } else if (initialMode == TwoMachinesPlay) {
1772           if (appData.noChessProgram) {
1773             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1774                               0, 2);
1775             return;
1776           }
1777           if (appData.icsActive) {
1778             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1779                               0, 2);
1780             return;
1781           }
1782           TwoMachinesEvent();
1783         } else if (initialMode == EditGame) {
1784           EditGameEvent();
1785         } else if (initialMode == EditPosition) {
1786           EditPositionEvent();
1787         } else if (initialMode == Training) {
1788           if (*appData.loadGameFile == NULLCHAR) {
1789             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1790             return;
1791           }
1792           TrainingEvent();
1793         }
1794     }
1795 }
1796
1797 void
1798 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1799 {
1800     DisplayBook(current+1);
1801
1802     MoveHistorySet( movelist, first, last, current, pvInfoList );
1803
1804     EvalGraphSet( first, last, current, pvInfoList );
1805
1806     MakeEngineOutputTitle();
1807 }
1808
1809 /*
1810  * Establish will establish a contact to a remote host.port.
1811  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1812  *  used to talk to the host.
1813  * Returns 0 if okay, error code if not.
1814  */
1815 int
1816 establish ()
1817 {
1818     char buf[MSG_SIZ];
1819
1820     if (*appData.icsCommPort != NULLCHAR) {
1821         /* Talk to the host through a serial comm port */
1822         return OpenCommPort(appData.icsCommPort, &icsPR);
1823
1824     } else if (*appData.gateway != NULLCHAR) {
1825         if (*appData.remoteShell == NULLCHAR) {
1826             /* Use the rcmd protocol to run telnet program on a gateway host */
1827             snprintf(buf, sizeof(buf), "%s %s %s",
1828                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1829             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1830
1831         } else {
1832             /* Use the rsh program to run telnet program on a gateway host */
1833             if (*appData.remoteUser == NULLCHAR) {
1834                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1835                         appData.gateway, appData.telnetProgram,
1836                         appData.icsHost, appData.icsPort);
1837             } else {
1838                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1839                         appData.remoteShell, appData.gateway,
1840                         appData.remoteUser, appData.telnetProgram,
1841                         appData.icsHost, appData.icsPort);
1842             }
1843             return StartChildProcess(buf, "", &icsPR);
1844
1845         }
1846     } else if (appData.useTelnet) {
1847         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1848
1849     } else {
1850         /* TCP socket interface differs somewhat between
1851            Unix and NT; handle details in the front end.
1852            */
1853         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1854     }
1855 }
1856
1857 void
1858 EscapeExpand (char *p, char *q)
1859 {       // [HGM] initstring: routine to shape up string arguments
1860         while(*p++ = *q++) if(p[-1] == '\\')
1861             switch(*q++) {
1862                 case 'n': p[-1] = '\n'; break;
1863                 case 'r': p[-1] = '\r'; break;
1864                 case 't': p[-1] = '\t'; break;
1865                 case '\\': p[-1] = '\\'; break;
1866                 case 0: *p = 0; return;
1867                 default: p[-1] = q[-1]; break;
1868             }
1869 }
1870
1871 void
1872 show_bytes (FILE *fp, char *buf, int count)
1873 {
1874     while (count--) {
1875         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1876             fprintf(fp, "\\%03o", *buf & 0xff);
1877         } else {
1878             putc(*buf, fp);
1879         }
1880         buf++;
1881     }
1882     fflush(fp);
1883 }
1884
1885 /* Returns an errno value */
1886 int
1887 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1888 {
1889     char buf[8192], *p, *q, *buflim;
1890     int left, newcount, outcount;
1891
1892     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1893         *appData.gateway != NULLCHAR) {
1894         if (appData.debugMode) {
1895             fprintf(debugFP, ">ICS: ");
1896             show_bytes(debugFP, message, count);
1897             fprintf(debugFP, "\n");
1898         }
1899         return OutputToProcess(pr, message, count, outError);
1900     }
1901
1902     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1903     p = message;
1904     q = buf;
1905     left = count;
1906     newcount = 0;
1907     while (left) {
1908         if (q >= buflim) {
1909             if (appData.debugMode) {
1910                 fprintf(debugFP, ">ICS: ");
1911                 show_bytes(debugFP, buf, newcount);
1912                 fprintf(debugFP, "\n");
1913             }
1914             outcount = OutputToProcess(pr, buf, newcount, outError);
1915             if (outcount < newcount) return -1; /* to be sure */
1916             q = buf;
1917             newcount = 0;
1918         }
1919         if (*p == '\n') {
1920             *q++ = '\r';
1921             newcount++;
1922         } else if (((unsigned char) *p) == TN_IAC) {
1923             *q++ = (char) TN_IAC;
1924             newcount ++;
1925         }
1926         *q++ = *p++;
1927         newcount++;
1928         left--;
1929     }
1930     if (appData.debugMode) {
1931         fprintf(debugFP, ">ICS: ");
1932         show_bytes(debugFP, buf, newcount);
1933         fprintf(debugFP, "\n");
1934     }
1935     outcount = OutputToProcess(pr, buf, newcount, outError);
1936     if (outcount < newcount) return -1; /* to be sure */
1937     return count;
1938 }
1939
1940 void
1941 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1942 {
1943     int outError, outCount;
1944     static int gotEof = 0;
1945     static FILE *ini;
1946
1947     /* Pass data read from player on to ICS */
1948     if (count > 0) {
1949         gotEof = 0;
1950         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1951         if (outCount < count) {
1952             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1953         }
1954         if(have_sent_ICS_logon == 2) {
1955           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1956             fprintf(ini, "%s", message);
1957             have_sent_ICS_logon = 3;
1958           } else
1959             have_sent_ICS_logon = 1;
1960         } else if(have_sent_ICS_logon == 3) {
1961             fprintf(ini, "%s", message);
1962             fclose(ini);
1963           have_sent_ICS_logon = 1;
1964         }
1965     } else if (count < 0) {
1966         RemoveInputSource(isr);
1967         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1968     } else if (gotEof++ > 0) {
1969         RemoveInputSource(isr);
1970         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1971     }
1972 }
1973
1974 void
1975 KeepAlive ()
1976 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1977     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1978     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1979     SendToICS("date\n");
1980     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1981 }
1982
1983 /* added routine for printf style output to ics */
1984 void
1985 ics_printf (char *format, ...)
1986 {
1987     char buffer[MSG_SIZ];
1988     va_list args;
1989
1990     va_start(args, format);
1991     vsnprintf(buffer, sizeof(buffer), format, args);
1992     buffer[sizeof(buffer)-1] = '\0';
1993     SendToICS(buffer);
1994     va_end(args);
1995 }
1996
1997 void
1998 SendToICS (char *s)
1999 {
2000     int count, outCount, outError;
2001
2002     if (icsPR == NoProc) return;
2003
2004     count = strlen(s);
2005     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2006     if (outCount < count) {
2007         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2008     }
2009 }
2010
2011 /* This is used for sending logon scripts to the ICS. Sending
2012    without a delay causes problems when using timestamp on ICC
2013    (at least on my machine). */
2014 void
2015 SendToICSDelayed (char *s, long msdelay)
2016 {
2017     int count, outCount, outError;
2018
2019     if (icsPR == NoProc) return;
2020
2021     count = strlen(s);
2022     if (appData.debugMode) {
2023         fprintf(debugFP, ">ICS: ");
2024         show_bytes(debugFP, s, count);
2025         fprintf(debugFP, "\n");
2026     }
2027     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2028                                       msdelay);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034
2035 /* Remove all highlighting escape sequences in s
2036    Also deletes any suffix starting with '('
2037    */
2038 char *
2039 StripHighlightAndTitle (char *s)
2040 {
2041     static char retbuf[MSG_SIZ];
2042     char *p = retbuf;
2043
2044     while (*s != NULLCHAR) {
2045         while (*s == '\033') {
2046             while (*s != NULLCHAR && !isalpha(*s)) s++;
2047             if (*s != NULLCHAR) s++;
2048         }
2049         while (*s != NULLCHAR && *s != '\033') {
2050             if (*s == '(' || *s == '[') {
2051                 *p = NULLCHAR;
2052                 return retbuf;
2053             }
2054             *p++ = *s++;
2055         }
2056     }
2057     *p = NULLCHAR;
2058     return retbuf;
2059 }
2060
2061 /* Remove all highlighting escape sequences in s */
2062 char *
2063 StripHighlight (char *s)
2064 {
2065     static char retbuf[MSG_SIZ];
2066     char *p = retbuf;
2067
2068     while (*s != NULLCHAR) {
2069         while (*s == '\033') {
2070             while (*s != NULLCHAR && !isalpha(*s)) s++;
2071             if (*s != NULLCHAR) s++;
2072         }
2073         while (*s != NULLCHAR && *s != '\033') {
2074             *p++ = *s++;
2075         }
2076     }
2077     *p = NULLCHAR;
2078     return retbuf;
2079 }
2080
2081 char engineVariant[MSG_SIZ];
2082 char *variantNames[] = VARIANT_NAMES;
2083 char *
2084 VariantName (VariantClass v)
2085 {
2086     if(v == VariantUnknown || *engineVariant) return engineVariant;
2087     return variantNames[v];
2088 }
2089
2090
2091 /* Identify a variant from the strings the chess servers use or the
2092    PGN Variant tag names we use. */
2093 VariantClass
2094 StringToVariant (char *e)
2095 {
2096     char *p;
2097     int wnum = -1;
2098     VariantClass v = VariantNormal;
2099     int i, found = FALSE;
2100     char buf[MSG_SIZ];
2101     int len;
2102
2103     if (!e) return v;
2104
2105     /* [HGM] skip over optional board-size prefixes */
2106     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2107         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2108         while( *e++ != '_');
2109     }
2110
2111     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2112         v = VariantNormal;
2113         found = TRUE;
2114     } else
2115     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2116       if (p = StrCaseStr(e, variantNames[i])) {
2117         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2118         v = (VariantClass) i;
2119         found = TRUE;
2120         break;
2121       }
2122     }
2123
2124     if (!found) {
2125       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2126           || StrCaseStr(e, "wild/fr")
2127           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2128         v = VariantFischeRandom;
2129       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2130                  (i = 1, p = StrCaseStr(e, "w"))) {
2131         p += i;
2132         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2133         if (isdigit(*p)) {
2134           wnum = atoi(p);
2135         } else {
2136           wnum = -1;
2137         }
2138         switch (wnum) {
2139         case 0: /* FICS only, actually */
2140         case 1:
2141           /* Castling legal even if K starts on d-file */
2142           v = VariantWildCastle;
2143           break;
2144         case 2:
2145         case 3:
2146         case 4:
2147           /* Castling illegal even if K & R happen to start in
2148              normal positions. */
2149           v = VariantNoCastle;
2150           break;
2151         case 5:
2152         case 7:
2153         case 8:
2154         case 10:
2155         case 11:
2156         case 12:
2157         case 13:
2158         case 14:
2159         case 15:
2160         case 18:
2161         case 19:
2162           /* Castling legal iff K & R start in normal positions */
2163           v = VariantNormal;
2164           break;
2165         case 6:
2166         case 20:
2167         case 21:
2168           /* Special wilds for position setup; unclear what to do here */
2169           v = VariantLoadable;
2170           break;
2171         case 9:
2172           /* Bizarre ICC game */
2173           v = VariantTwoKings;
2174           break;
2175         case 16:
2176           v = VariantKriegspiel;
2177           break;
2178         case 17:
2179           v = VariantLosers;
2180           break;
2181         case 22:
2182           v = VariantFischeRandom;
2183           break;
2184         case 23:
2185           v = VariantCrazyhouse;
2186           break;
2187         case 24:
2188           v = VariantBughouse;
2189           break;
2190         case 25:
2191           v = Variant3Check;
2192           break;
2193         case 26:
2194           /* Not quite the same as FICS suicide! */
2195           v = VariantGiveaway;
2196           break;
2197         case 27:
2198           v = VariantAtomic;
2199           break;
2200         case 28:
2201           v = VariantShatranj;
2202           break;
2203
2204         /* Temporary names for future ICC types.  The name *will* change in
2205            the next xboard/WinBoard release after ICC defines it. */
2206         case 29:
2207           v = Variant29;
2208           break;
2209         case 30:
2210           v = Variant30;
2211           break;
2212         case 31:
2213           v = Variant31;
2214           break;
2215         case 32:
2216           v = Variant32;
2217           break;
2218         case 33:
2219           v = Variant33;
2220           break;
2221         case 34:
2222           v = Variant34;
2223           break;
2224         case 35:
2225           v = Variant35;
2226           break;
2227         case 36:
2228           v = Variant36;
2229           break;
2230         case 37:
2231           v = VariantShogi;
2232           break;
2233         case 38:
2234           v = VariantXiangqi;
2235           break;
2236         case 39:
2237           v = VariantCourier;
2238           break;
2239         case 40:
2240           v = VariantGothic;
2241           break;
2242         case 41:
2243           v = VariantCapablanca;
2244           break;
2245         case 42:
2246           v = VariantKnightmate;
2247           break;
2248         case 43:
2249           v = VariantFairy;
2250           break;
2251         case 44:
2252           v = VariantCylinder;
2253           break;
2254         case 45:
2255           v = VariantFalcon;
2256           break;
2257         case 46:
2258           v = VariantCapaRandom;
2259           break;
2260         case 47:
2261           v = VariantBerolina;
2262           break;
2263         case 48:
2264           v = VariantJanus;
2265           break;
2266         case 49:
2267           v = VariantSuper;
2268           break;
2269         case 50:
2270           v = VariantGreat;
2271           break;
2272         case -1:
2273           /* Found "wild" or "w" in the string but no number;
2274              must assume it's normal chess. */
2275           v = VariantNormal;
2276           break;
2277         default:
2278           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2279           if( (len >= MSG_SIZ) && appData.debugMode )
2280             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2281
2282           DisplayError(buf, 0);
2283           v = VariantUnknown;
2284           break;
2285         }
2286       }
2287     }
2288     if (appData.debugMode) {
2289       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2290               e, wnum, VariantName(v));
2291     }
2292     return v;
2293 }
2294
2295 static int leftover_start = 0, leftover_len = 0;
2296 char star_match[STAR_MATCH_N][MSG_SIZ];
2297
2298 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2299    advance *index beyond it, and set leftover_start to the new value of
2300    *index; else return FALSE.  If pattern contains the character '*', it
2301    matches any sequence of characters not containing '\r', '\n', or the
2302    character following the '*' (if any), and the matched sequence(s) are
2303    copied into star_match.
2304    */
2305 int
2306 looking_at ( char *buf, int *index, char *pattern)
2307 {
2308     char *bufp = &buf[*index], *patternp = pattern;
2309     int star_count = 0;
2310     char *matchp = star_match[0];
2311
2312     for (;;) {
2313         if (*patternp == NULLCHAR) {
2314             *index = leftover_start = bufp - buf;
2315             *matchp = NULLCHAR;
2316             return TRUE;
2317         }
2318         if (*bufp == NULLCHAR) return FALSE;
2319         if (*patternp == '*') {
2320             if (*bufp == *(patternp + 1)) {
2321                 *matchp = NULLCHAR;
2322                 matchp = star_match[++star_count];
2323                 patternp += 2;
2324                 bufp++;
2325                 continue;
2326             } else if (*bufp == '\n' || *bufp == '\r') {
2327                 patternp++;
2328                 if (*patternp == NULLCHAR)
2329                   continue;
2330                 else
2331                   return FALSE;
2332             } else {
2333                 *matchp++ = *bufp++;
2334                 continue;
2335             }
2336         }
2337         if (*patternp != *bufp) return FALSE;
2338         patternp++;
2339         bufp++;
2340     }
2341 }
2342
2343 void
2344 SendToPlayer (char *data, int length)
2345 {
2346     int error, outCount;
2347     outCount = OutputToProcess(NoProc, data, length, &error);
2348     if (outCount < length) {
2349         DisplayFatalError(_("Error writing to display"), error, 1);
2350     }
2351 }
2352
2353 void
2354 PackHolding (char packed[], char *holding)
2355 {
2356     char *p = holding;
2357     char *q = packed;
2358     int runlength = 0;
2359     int curr = 9999;
2360     do {
2361         if (*p == curr) {
2362             runlength++;
2363         } else {
2364             switch (runlength) {
2365               case 0:
2366                 break;
2367               case 1:
2368                 *q++ = curr;
2369                 break;
2370               case 2:
2371                 *q++ = curr;
2372                 *q++ = curr;
2373                 break;
2374               default:
2375                 sprintf(q, "%d", runlength);
2376                 while (*q) q++;
2377                 *q++ = curr;
2378                 break;
2379             }
2380             runlength = 1;
2381             curr = *p;
2382         }
2383     } while (*p++);
2384     *q = NULLCHAR;
2385 }
2386
2387 /* Telnet protocol requests from the front end */
2388 void
2389 TelnetRequest (unsigned char ddww, unsigned char option)
2390 {
2391     unsigned char msg[3];
2392     int outCount, outError;
2393
2394     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2395
2396     if (appData.debugMode) {
2397         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2398         switch (ddww) {
2399           case TN_DO:
2400             ddwwStr = "DO";
2401             break;
2402           case TN_DONT:
2403             ddwwStr = "DONT";
2404             break;
2405           case TN_WILL:
2406             ddwwStr = "WILL";
2407             break;
2408           case TN_WONT:
2409             ddwwStr = "WONT";
2410             break;
2411           default:
2412             ddwwStr = buf1;
2413             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2414             break;
2415         }
2416         switch (option) {
2417           case TN_ECHO:
2418             optionStr = "ECHO";
2419             break;
2420           default:
2421             optionStr = buf2;
2422             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2423             break;
2424         }
2425         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2426     }
2427     msg[0] = TN_IAC;
2428     msg[1] = ddww;
2429     msg[2] = option;
2430     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2431     if (outCount < 3) {
2432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2433     }
2434 }
2435
2436 void
2437 DoEcho ()
2438 {
2439     if (!appData.icsActive) return;
2440     TelnetRequest(TN_DO, TN_ECHO);
2441 }
2442
2443 void
2444 DontEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DONT, TN_ECHO);
2448 }
2449
2450 void
2451 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2452 {
2453     /* put the holdings sent to us by the server on the board holdings area */
2454     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2455     char p;
2456     ChessSquare piece;
2457
2458     if(gameInfo.holdingsWidth < 2)  return;
2459     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2460         return; // prevent overwriting by pre-board holdings
2461
2462     if( (int)lowestPiece >= BlackPawn ) {
2463         holdingsColumn = 0;
2464         countsColumn = 1;
2465         holdingsStartRow = BOARD_HEIGHT-1;
2466         direction = -1;
2467     } else {
2468         holdingsColumn = BOARD_WIDTH-1;
2469         countsColumn = BOARD_WIDTH-2;
2470         holdingsStartRow = 0;
2471         direction = 1;
2472     }
2473
2474     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2475         board[i][holdingsColumn] = EmptySquare;
2476         board[i][countsColumn]   = (ChessSquare) 0;
2477     }
2478     while( (p=*holdings++) != NULLCHAR ) {
2479         piece = CharToPiece( ToUpper(p) );
2480         if(piece == EmptySquare) continue;
2481         /*j = (int) piece - (int) WhitePawn;*/
2482         j = PieceToNumber(piece);
2483         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2484         if(j < 0) continue;               /* should not happen */
2485         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2486         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2487         board[holdingsStartRow+j*direction][countsColumn]++;
2488     }
2489 }
2490
2491
2492 void
2493 VariantSwitch (Board board, VariantClass newVariant)
2494 {
2495    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2496    static Board oldBoard;
2497
2498    startedFromPositionFile = FALSE;
2499    if(gameInfo.variant == newVariant) return;
2500
2501    /* [HGM] This routine is called each time an assignment is made to
2502     * gameInfo.variant during a game, to make sure the board sizes
2503     * are set to match the new variant. If that means adding or deleting
2504     * holdings, we shift the playing board accordingly
2505     * This kludge is needed because in ICS observe mode, we get boards
2506     * of an ongoing game without knowing the variant, and learn about the
2507     * latter only later. This can be because of the move list we requested,
2508     * in which case the game history is refilled from the beginning anyway,
2509     * but also when receiving holdings of a crazyhouse game. In the latter
2510     * case we want to add those holdings to the already received position.
2511     */
2512
2513
2514    if (appData.debugMode) {
2515      fprintf(debugFP, "Switch board from %s to %s\n",
2516              VariantName(gameInfo.variant), VariantName(newVariant));
2517      setbuf(debugFP, NULL);
2518    }
2519    shuffleOpenings = 0;       /* [HGM] shuffle */
2520    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2521    switch(newVariant)
2522      {
2523      case VariantShogi:
2524        newWidth = 9;  newHeight = 9;
2525        gameInfo.holdingsSize = 7;
2526      case VariantBughouse:
2527      case VariantCrazyhouse:
2528        newHoldingsWidth = 2; break;
2529      case VariantGreat:
2530        newWidth = 10;
2531      case VariantSuper:
2532        newHoldingsWidth = 2;
2533        gameInfo.holdingsSize = 8;
2534        break;
2535      case VariantGothic:
2536      case VariantCapablanca:
2537      case VariantCapaRandom:
2538        newWidth = 10;
2539      default:
2540        newHoldingsWidth = gameInfo.holdingsSize = 0;
2541      };
2542
2543    if(newWidth  != gameInfo.boardWidth  ||
2544       newHeight != gameInfo.boardHeight ||
2545       newHoldingsWidth != gameInfo.holdingsWidth ) {
2546
2547      /* shift position to new playing area, if needed */
2548      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2549        for(i=0; i<BOARD_HEIGHT; i++)
2550          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2551            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2552              board[i][j];
2553        for(i=0; i<newHeight; i++) {
2554          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2555          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2556        }
2557      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2558        for(i=0; i<BOARD_HEIGHT; i++)
2559          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2560            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2561              board[i][j];
2562      }
2563      board[HOLDINGS_SET] = 0;
2564      gameInfo.boardWidth  = newWidth;
2565      gameInfo.boardHeight = newHeight;
2566      gameInfo.holdingsWidth = newHoldingsWidth;
2567      gameInfo.variant = newVariant;
2568      InitDrawingSizes(-2, 0);
2569    } else gameInfo.variant = newVariant;
2570    CopyBoard(oldBoard, board);   // remember correctly formatted board
2571      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2572    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2573 }
2574
2575 static int loggedOn = FALSE;
2576
2577 /*-- Game start info cache: --*/
2578 int gs_gamenum;
2579 char gs_kind[MSG_SIZ];
2580 static char player1Name[128] = "";
2581 static char player2Name[128] = "";
2582 static char cont_seq[] = "\n\\   ";
2583 static int player1Rating = -1;
2584 static int player2Rating = -1;
2585 /*----------------------------*/
2586
2587 ColorClass curColor = ColorNormal;
2588 int suppressKibitz = 0;
2589
2590 // [HGM] seekgraph
2591 Boolean soughtPending = FALSE;
2592 Boolean seekGraphUp;
2593 #define MAX_SEEK_ADS 200
2594 #define SQUARE 0x80
2595 char *seekAdList[MAX_SEEK_ADS];
2596 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2597 float tcList[MAX_SEEK_ADS];
2598 char colorList[MAX_SEEK_ADS];
2599 int nrOfSeekAds = 0;
2600 int minRating = 1010, maxRating = 2800;
2601 int hMargin = 10, vMargin = 20, h, w;
2602 extern int squareSize, lineGap;
2603
2604 void
2605 PlotSeekAd (int i)
2606 {
2607         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2608         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2609         if(r < minRating+100 && r >=0 ) r = minRating+100;
2610         if(r > maxRating) r = maxRating;
2611         if(tc < 1.f) tc = 1.f;
2612         if(tc > 95.f) tc = 95.f;
2613         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2614         y = ((double)r - minRating)/(maxRating - minRating)
2615             * (h-vMargin-squareSize/8-1) + vMargin;
2616         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2617         if(strstr(seekAdList[i], " u ")) color = 1;
2618         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2619            !strstr(seekAdList[i], "bullet") &&
2620            !strstr(seekAdList[i], "blitz") &&
2621            !strstr(seekAdList[i], "standard") ) color = 2;
2622         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2623         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2624 }
2625
2626 void
2627 PlotSingleSeekAd (int i)
2628 {
2629         PlotSeekAd(i);
2630 }
2631
2632 void
2633 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2634 {
2635         char buf[MSG_SIZ], *ext = "";
2636         VariantClass v = StringToVariant(type);
2637         if(strstr(type, "wild")) {
2638             ext = type + 4; // append wild number
2639             if(v == VariantFischeRandom) type = "chess960"; else
2640             if(v == VariantLoadable) type = "setup"; else
2641             type = VariantName(v);
2642         }
2643         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2644         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2645             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2646             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2647             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2648             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2649             seekNrList[nrOfSeekAds] = nr;
2650             zList[nrOfSeekAds] = 0;
2651             seekAdList[nrOfSeekAds++] = StrSave(buf);
2652             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2653         }
2654 }
2655
2656 void
2657 EraseSeekDot (int i)
2658 {
2659     int x = xList[i], y = yList[i], d=squareSize/4, k;
2660     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2661     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2662     // now replot every dot that overlapped
2663     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2664         int xx = xList[k], yy = yList[k];
2665         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2666             DrawSeekDot(xx, yy, colorList[k]);
2667     }
2668 }
2669
2670 void
2671 RemoveSeekAd (int nr)
2672 {
2673         int i;
2674         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2675             EraseSeekDot(i);
2676             if(seekAdList[i]) free(seekAdList[i]);
2677             seekAdList[i] = seekAdList[--nrOfSeekAds];
2678             seekNrList[i] = seekNrList[nrOfSeekAds];
2679             ratingList[i] = ratingList[nrOfSeekAds];
2680             colorList[i]  = colorList[nrOfSeekAds];
2681             tcList[i] = tcList[nrOfSeekAds];
2682             xList[i]  = xList[nrOfSeekAds];
2683             yList[i]  = yList[nrOfSeekAds];
2684             zList[i]  = zList[nrOfSeekAds];
2685             seekAdList[nrOfSeekAds] = NULL;
2686             break;
2687         }
2688 }
2689
2690 Boolean
2691 MatchSoughtLine (char *line)
2692 {
2693     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2694     int nr, base, inc, u=0; char dummy;
2695
2696     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2697        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2698        (u=1) &&
2699        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2700         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2701         // match: compact and save the line
2702         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2703         return TRUE;
2704     }
2705     return FALSE;
2706 }
2707
2708 int
2709 DrawSeekGraph ()
2710 {
2711     int i;
2712     if(!seekGraphUp) return FALSE;
2713     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2714     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2715
2716     DrawSeekBackground(0, 0, w, h);
2717     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2718     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2719     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2720         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2721         yy = h-1-yy;
2722         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2723         if(i%500 == 0) {
2724             char buf[MSG_SIZ];
2725             snprintf(buf, MSG_SIZ, "%d", i);
2726             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2727         }
2728     }
2729     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2730     for(i=1; i<100; i+=(i<10?1:5)) {
2731         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2732         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2733         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2734             char buf[MSG_SIZ];
2735             snprintf(buf, MSG_SIZ, "%d", i);
2736             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2737         }
2738     }
2739     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2740     return TRUE;
2741 }
2742
2743 int
2744 SeekGraphClick (ClickType click, int x, int y, int moving)
2745 {
2746     static int lastDown = 0, displayed = 0, lastSecond;
2747     if(y < 0) return FALSE;
2748     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2749         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2750         if(!seekGraphUp) return FALSE;
2751         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2752         DrawPosition(TRUE, NULL);
2753         return TRUE;
2754     }
2755     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2756         if(click == Release || moving) return FALSE;
2757         nrOfSeekAds = 0;
2758         soughtPending = TRUE;
2759         SendToICS(ics_prefix);
2760         SendToICS("sought\n"); // should this be "sought all"?
2761     } else { // issue challenge based on clicked ad
2762         int dist = 10000; int i, closest = 0, second = 0;
2763         for(i=0; i<nrOfSeekAds; i++) {
2764             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2765             if(d < dist) { dist = d; closest = i; }
2766             second += (d - zList[i] < 120); // count in-range ads
2767             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2768         }
2769         if(dist < 120) {
2770             char buf[MSG_SIZ];
2771             second = (second > 1);
2772             if(displayed != closest || second != lastSecond) {
2773                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2774                 lastSecond = second; displayed = closest;
2775             }
2776             if(click == Press) {
2777                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2778                 lastDown = closest;
2779                 return TRUE;
2780             } // on press 'hit', only show info
2781             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2782             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2783             SendToICS(ics_prefix);
2784             SendToICS(buf);
2785             return TRUE; // let incoming board of started game pop down the graph
2786         } else if(click == Release) { // release 'miss' is ignored
2787             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2788             if(moving == 2) { // right up-click
2789                 nrOfSeekAds = 0; // refresh graph
2790                 soughtPending = TRUE;
2791                 SendToICS(ics_prefix);
2792                 SendToICS("sought\n"); // should this be "sought all"?
2793             }
2794             return TRUE;
2795         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2796         // press miss or release hit 'pop down' seek graph
2797         seekGraphUp = FALSE;
2798         DrawPosition(TRUE, NULL);
2799     }
2800     return TRUE;
2801 }
2802
2803 void
2804 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2805 {
2806 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2807 #define STARTED_NONE 0
2808 #define STARTED_MOVES 1
2809 #define STARTED_BOARD 2
2810 #define STARTED_OBSERVE 3
2811 #define STARTED_HOLDINGS 4
2812 #define STARTED_CHATTER 5
2813 #define STARTED_COMMENT 6
2814 #define STARTED_MOVES_NOHIDE 7
2815
2816     static int started = STARTED_NONE;
2817     static char parse[20000];
2818     static int parse_pos = 0;
2819     static char buf[BUF_SIZE + 1];
2820     static int firstTime = TRUE, intfSet = FALSE;
2821     static ColorClass prevColor = ColorNormal;
2822     static int savingComment = FALSE;
2823     static int cmatch = 0; // continuation sequence match
2824     char *bp;
2825     char str[MSG_SIZ];
2826     int i, oldi;
2827     int buf_len;
2828     int next_out;
2829     int tkind;
2830     int backup;    /* [DM] For zippy color lines */
2831     char *p;
2832     char talker[MSG_SIZ]; // [HGM] chat
2833     int channel, collective=0;
2834
2835     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2836
2837     if (appData.debugMode) {
2838       if (!error) {
2839         fprintf(debugFP, "<ICS: ");
2840         show_bytes(debugFP, data, count);
2841         fprintf(debugFP, "\n");
2842       }
2843     }
2844
2845     if (appData.debugMode) { int f = forwardMostMove;
2846         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2847                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2848                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2849     }
2850     if (count > 0) {
2851         /* If last read ended with a partial line that we couldn't parse,
2852            prepend it to the new read and try again. */
2853         if (leftover_len > 0) {
2854             for (i=0; i<leftover_len; i++)
2855               buf[i] = buf[leftover_start + i];
2856         }
2857
2858     /* copy new characters into the buffer */
2859     bp = buf + leftover_len;
2860     buf_len=leftover_len;
2861     for (i=0; i<count; i++)
2862     {
2863         // ignore these
2864         if (data[i] == '\r')
2865             continue;
2866
2867         // join lines split by ICS?
2868         if (!appData.noJoin)
2869         {
2870             /*
2871                 Joining just consists of finding matches against the
2872                 continuation sequence, and discarding that sequence
2873                 if found instead of copying it.  So, until a match
2874                 fails, there's nothing to do since it might be the
2875                 complete sequence, and thus, something we don't want
2876                 copied.
2877             */
2878             if (data[i] == cont_seq[cmatch])
2879             {
2880                 cmatch++;
2881                 if (cmatch == strlen(cont_seq))
2882                 {
2883                     cmatch = 0; // complete match.  just reset the counter
2884
2885                     /*
2886                         it's possible for the ICS to not include the space
2887                         at the end of the last word, making our [correct]
2888                         join operation fuse two separate words.  the server
2889                         does this when the space occurs at the width setting.
2890                     */
2891                     if (!buf_len || buf[buf_len-1] != ' ')
2892                     {
2893                         *bp++ = ' ';
2894                         buf_len++;
2895                     }
2896                 }
2897                 continue;
2898             }
2899             else if (cmatch)
2900             {
2901                 /*
2902                     match failed, so we have to copy what matched before
2903                     falling through and copying this character.  In reality,
2904                     this will only ever be just the newline character, but
2905                     it doesn't hurt to be precise.
2906                 */
2907                 strncpy(bp, cont_seq, cmatch);
2908                 bp += cmatch;
2909                 buf_len += cmatch;
2910                 cmatch = 0;
2911             }
2912         }
2913
2914         // copy this char
2915         *bp++ = data[i];
2916         buf_len++;
2917     }
2918
2919         buf[buf_len] = NULLCHAR;
2920 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2921         next_out = 0;
2922         leftover_start = 0;
2923
2924         i = 0;
2925         while (i < buf_len) {
2926             /* Deal with part of the TELNET option negotiation
2927                protocol.  We refuse to do anything beyond the
2928                defaults, except that we allow the WILL ECHO option,
2929                which ICS uses to turn off password echoing when we are
2930                directly connected to it.  We reject this option
2931                if localLineEditing mode is on (always on in xboard)
2932                and we are talking to port 23, which might be a real
2933                telnet server that will try to keep WILL ECHO on permanently.
2934              */
2935             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2936                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2937                 unsigned char option;
2938                 oldi = i;
2939                 switch ((unsigned char) buf[++i]) {
2940                   case TN_WILL:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<WILL ");
2943                     switch (option = (unsigned char) buf[++i]) {
2944                       case TN_ECHO:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "ECHO ");
2947                         /* Reply only if this is a change, according
2948                            to the protocol rules. */
2949                         if (remoteEchoOption) break;
2950                         if (appData.localLineEditing &&
2951                             atoi(appData.icsPort) == TN_PORT) {
2952                             TelnetRequest(TN_DONT, TN_ECHO);
2953                         } else {
2954                             EchoOff();
2955                             TelnetRequest(TN_DO, TN_ECHO);
2956                             remoteEchoOption = TRUE;
2957                         }
2958                         break;
2959                       default:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         /* Whatever this is, we don't want it. */
2963                         TelnetRequest(TN_DONT, option);
2964                         break;
2965                     }
2966                     break;
2967                   case TN_WONT:
2968                     if (appData.debugMode)
2969                       fprintf(debugFP, "\n<WONT ");
2970                     switch (option = (unsigned char) buf[++i]) {
2971                       case TN_ECHO:
2972                         if (appData.debugMode)
2973                           fprintf(debugFP, "ECHO ");
2974                         /* Reply only if this is a change, according
2975                            to the protocol rules. */
2976                         if (!remoteEchoOption) break;
2977                         EchoOn();
2978                         TelnetRequest(TN_DONT, TN_ECHO);
2979                         remoteEchoOption = FALSE;
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", (unsigned char) option);
2984                         /* Whatever this is, it must already be turned
2985                            off, because we never agree to turn on
2986                            anything non-default, so according to the
2987                            protocol rules, we don't reply. */
2988                         break;
2989                     }
2990                     break;
2991                   case TN_DO:
2992                     if (appData.debugMode)
2993                       fprintf(debugFP, "\n<DO ");
2994                     switch (option = (unsigned char) buf[++i]) {
2995                       default:
2996                         /* Whatever this is, we refuse to do it. */
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", option);
2999                         TelnetRequest(TN_WONT, option);
3000                         break;
3001                     }
3002                     break;
3003                   case TN_DONT:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<DONT ");
3006                     switch (option = (unsigned char) buf[++i]) {
3007                       default:
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         /* Whatever this is, we are already not doing
3011                            it, because we never agree to do anything
3012                            non-default, so according to the protocol
3013                            rules, we don't reply. */
3014                         break;
3015                     }
3016                     break;
3017                   case TN_IAC:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<IAC ");
3020                     /* Doubled IAC; pass it through */
3021                     i--;
3022                     break;
3023                   default:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3026                     /* Drop all other telnet commands on the floor */
3027                     break;
3028                 }
3029                 if (oldi > next_out)
3030                   SendToPlayer(&buf[next_out], oldi - next_out);
3031                 if (++i > next_out)
3032                   next_out = i;
3033                 continue;
3034             }
3035
3036             /* OK, this at least will *usually* work */
3037             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3038                 loggedOn = TRUE;
3039             }
3040
3041             if (loggedOn && !intfSet) {
3042                 if (ics_type == ICS_ICC) {
3043                   snprintf(str, MSG_SIZ,
3044                           "/set-quietly interface %s\n/set-quietly style 12\n",
3045                           programVersion);
3046                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3047                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3048                 } else if (ics_type == ICS_CHESSNET) {
3049                   snprintf(str, MSG_SIZ, "/style 12\n");
3050                 } else {
3051                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3052                   strcat(str, programVersion);
3053                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3054                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3055                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3056 #ifdef WIN32
3057                   strcat(str, "$iset nohighlight 1\n");
3058 #endif
3059                   strcat(str, "$iset lock 1\n$style 12\n");
3060                 }
3061                 SendToICS(str);
3062                 NotifyFrontendLogin();
3063                 intfSet = TRUE;
3064             }
3065
3066             if (started == STARTED_COMMENT) {
3067                 /* Accumulate characters in comment */
3068                 parse[parse_pos++] = buf[i];
3069                 if (buf[i] == '\n') {
3070                     parse[parse_pos] = NULLCHAR;
3071                     if(chattingPartner>=0) {
3072                         char mess[MSG_SIZ];
3073                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3074                         OutputChatMessage(chattingPartner, mess);
3075                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3076                             int p;
3077                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3078                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3079                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3080                                 OutputChatMessage(p, mess);
3081                                 break;
3082                             }
3083                         }
3084                         chattingPartner = -1;
3085                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3086                         collective = 0;
3087                     } else
3088                     if(!suppressKibitz) // [HGM] kibitz
3089                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3090                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3091                         int nrDigit = 0, nrAlph = 0, j;
3092                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3093                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3094                         parse[parse_pos] = NULLCHAR;
3095                         // try to be smart: if it does not look like search info, it should go to
3096                         // ICS interaction window after all, not to engine-output window.
3097                         for(j=0; j<parse_pos; j++) { // count letters and digits
3098                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3099                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3100                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3101                         }
3102                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3103                             int depth=0; float score;
3104                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3105                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3106                                 pvInfoList[forwardMostMove-1].depth = depth;
3107                                 pvInfoList[forwardMostMove-1].score = 100*score;
3108                             }
3109                             OutputKibitz(suppressKibitz, parse);
3110                         } else {
3111                             char tmp[MSG_SIZ];
3112                             if(gameMode == IcsObserving) // restore original ICS messages
3113                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3114                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3115                             else
3116                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3117                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3118                             SendToPlayer(tmp, strlen(tmp));
3119                         }
3120                         next_out = i+1; // [HGM] suppress printing in ICS window
3121                     }
3122                     started = STARTED_NONE;
3123                 } else {
3124                     /* Don't match patterns against characters in comment */
3125                     i++;
3126                     continue;
3127                 }
3128             }
3129             if (started == STARTED_CHATTER) {
3130                 if (buf[i] != '\n') {
3131                     /* Don't match patterns against characters in chatter */
3132                     i++;
3133                     continue;
3134                 }
3135                 started = STARTED_NONE;
3136                 if(suppressKibitz) next_out = i+1;
3137             }
3138
3139             /* Kludge to deal with rcmd protocol */
3140             if (firstTime && looking_at(buf, &i, "\001*")) {
3141                 DisplayFatalError(&buf[1], 0, 1);
3142                 continue;
3143             } else {
3144                 firstTime = FALSE;
3145             }
3146
3147             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3148                 ics_type = ICS_ICC;
3149                 ics_prefix = "/";
3150                 if (appData.debugMode)
3151                   fprintf(debugFP, "ics_type %d\n", ics_type);
3152                 continue;
3153             }
3154             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3155                 ics_type = ICS_FICS;
3156                 ics_prefix = "$";
3157                 if (appData.debugMode)
3158                   fprintf(debugFP, "ics_type %d\n", ics_type);
3159                 continue;
3160             }
3161             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3162                 ics_type = ICS_CHESSNET;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168
3169             if (!loggedOn &&
3170                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3171                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3172                  looking_at(buf, &i, "will be \"*\""))) {
3173               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3174               continue;
3175             }
3176
3177             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3178               char buf[MSG_SIZ];
3179               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3180               DisplayIcsInteractionTitle(buf);
3181               have_set_title = TRUE;
3182             }
3183
3184             /* skip finger notes */
3185             if (started == STARTED_NONE &&
3186                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3187                  (buf[i] == '1' && buf[i+1] == '0')) &&
3188                 buf[i+2] == ':' && buf[i+3] == ' ') {
3189               started = STARTED_CHATTER;
3190               i += 3;
3191               continue;
3192             }
3193
3194             oldi = i;
3195             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3196             if(appData.seekGraph) {
3197                 if(soughtPending && MatchSoughtLine(buf+i)) {
3198                     i = strstr(buf+i, "rated") - buf;
3199                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200                     next_out = leftover_start = i;
3201                     started = STARTED_CHATTER;
3202                     suppressKibitz = TRUE;
3203                     continue;
3204                 }
3205                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3206                         && looking_at(buf, &i, "* ads displayed")) {
3207                     soughtPending = FALSE;
3208                     seekGraphUp = TRUE;
3209                     DrawSeekGraph();
3210                     continue;
3211                 }
3212                 if(appData.autoRefresh) {
3213                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3214                         int s = (ics_type == ICS_ICC); // ICC format differs
3215                         if(seekGraphUp)
3216                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3217                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3218                         looking_at(buf, &i, "*% "); // eat prompt
3219                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3220                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = i; // suppress
3222                         continue;
3223                     }
3224                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3225                         char *p = star_match[0];
3226                         while(*p) {
3227                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3228                             while(*p && *p++ != ' '); // next
3229                         }
3230                         looking_at(buf, &i, "*% "); // eat prompt
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i;
3233                         continue;
3234                     }
3235                 }
3236             }
3237
3238             /* skip formula vars */
3239             if (started == STARTED_NONE &&
3240                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3241               started = STARTED_CHATTER;
3242               i += 3;
3243               continue;
3244             }
3245
3246             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3247             if (appData.autoKibitz && started == STARTED_NONE &&
3248                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3249                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3250                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3251                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3252                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3253                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3254                         suppressKibitz = TRUE;
3255                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3256                         next_out = i;
3257                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3258                                 && (gameMode == IcsPlayingWhite)) ||
3259                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3260                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3261                             started = STARTED_CHATTER; // own kibitz we simply discard
3262                         else {
3263                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3264                             parse_pos = 0; parse[0] = NULLCHAR;
3265                             savingComment = TRUE;
3266                             suppressKibitz = gameMode != IcsObserving ? 2 :
3267                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3268                         }
3269                         continue;
3270                 } else
3271                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3272                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3273                          && atoi(star_match[0])) {
3274                     // suppress the acknowledgements of our own autoKibitz
3275                     char *p;
3276                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3278                     SendToPlayer(star_match[0], strlen(star_match[0]));
3279                     if(looking_at(buf, &i, "*% ")) // eat prompt
3280                         suppressKibitz = FALSE;
3281                     next_out = i;
3282                     continue;
3283                 }
3284             } // [HGM] kibitz: end of patch
3285
3286             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3287
3288             // [HGM] chat: intercept tells by users for which we have an open chat window
3289             channel = -1;
3290             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3291                                            looking_at(buf, &i, "* whispers:") ||
3292                                            looking_at(buf, &i, "* kibitzes:") ||
3293                                            looking_at(buf, &i, "* shouts:") ||
3294                                            looking_at(buf, &i, "* c-shouts:") ||
3295                                            looking_at(buf, &i, "--> * ") ||
3296                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3297                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3298                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3299                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3300                 int p;
3301                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3302                 chattingPartner = -1; collective = 0;
3303
3304                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3305                 for(p=0; p<MAX_CHAT; p++) {
3306                     collective = 1;
3307                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3308                     talker[0] = '['; strcat(talker, "] ");
3309                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3310                     chattingPartner = p; break;
3311                     }
3312                 } else
3313                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3314                 for(p=0; p<MAX_CHAT; p++) {
3315                     collective = 1;
3316                     if(!strcmp("kibitzes", chatPartner[p])) {
3317                         talker[0] = '['; strcat(talker, "] ");
3318                         chattingPartner = p; break;
3319                     }
3320                 } else
3321                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3322                 for(p=0; p<MAX_CHAT; p++) {
3323                     collective = 1;
3324                     if(!strcmp("whispers", chatPartner[p])) {
3325                         talker[0] = '['; strcat(talker, "] ");
3326                         chattingPartner = p; break;
3327                     }
3328                 } else
3329                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3330                   if(buf[i-8] == '-' && buf[i-3] == 't')
3331                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3332                     collective = 1;
3333                     if(!strcmp("c-shouts", chatPartner[p])) {
3334                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3335                         chattingPartner = p; break;
3336                     }
3337                   }
3338                   if(chattingPartner < 0)
3339                   for(p=0; p<MAX_CHAT; p++) {
3340                     collective = 1;
3341                     if(!strcmp("shouts", chatPartner[p])) {
3342                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3343                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3344                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3345                         chattingPartner = p; break;
3346                     }
3347                   }
3348                 }
3349                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3350                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3351                     talker[0] = 0;
3352                     Colorize(ColorTell, FALSE);
3353                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3354                     collective |= 2;
3355                     chattingPartner = p; break;
3356                 }
3357                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3358                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3359                     started = STARTED_COMMENT;
3360                     parse_pos = 0; parse[0] = NULLCHAR;
3361                     savingComment = 3 + chattingPartner; // counts as TRUE
3362                     if(collective == 3) i = oldi; else {
3363                         suppressKibitz = TRUE;
3364                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3365                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3366                         continue;
3367                     }
3368                 }
3369             } // [HGM] chat: end of patch
3370
3371           backup = i;
3372             if (appData.zippyTalk || appData.zippyPlay) {
3373                 /* [DM] Backup address for color zippy lines */
3374 #if ZIPPY
3375                if (loggedOn == TRUE)
3376                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3377                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3378 #endif
3379             } // [DM] 'else { ' deleted
3380                 if (
3381                     /* Regular tells and says */
3382                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3383                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3384                     looking_at(buf, &i, "* says: ") ||
3385                     /* Don't color "message" or "messages" output */
3386                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3387                     looking_at(buf, &i, "*. * at *:*: ") ||
3388                     looking_at(buf, &i, "--* (*:*): ") ||
3389                     /* Message notifications (same color as tells) */
3390                     looking_at(buf, &i, "* has left a message ") ||
3391                     looking_at(buf, &i, "* just sent you a message:\n") ||
3392                     /* Whispers and kibitzes */
3393                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3394                     looking_at(buf, &i, "* kibitzes: ") ||
3395                     /* Channel tells */
3396                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3397
3398                   if (tkind == 1 && strchr(star_match[0], ':')) {
3399                       /* Avoid "tells you:" spoofs in channels */
3400                      tkind = 3;
3401                   }
3402                   if (star_match[0][0] == NULLCHAR ||
3403                       strchr(star_match[0], ' ') ||
3404                       (tkind == 3 && strchr(star_match[1], ' '))) {
3405                     /* Reject bogus matches */
3406                     i = oldi;
3407                   } else {
3408                     if (appData.colorize) {
3409                       if (oldi > next_out) {
3410                         SendToPlayer(&buf[next_out], oldi - next_out);
3411                         next_out = oldi;
3412                       }
3413                       switch (tkind) {
3414                       case 1:
3415                         Colorize(ColorTell, FALSE);
3416                         curColor = ColorTell;
3417                         break;
3418                       case 2:
3419                         Colorize(ColorKibitz, FALSE);
3420                         curColor = ColorKibitz;
3421                         break;
3422                       case 3:
3423                         p = strrchr(star_match[1], '(');
3424                         if (p == NULL) {
3425                           p = star_match[1];
3426                         } else {
3427                           p++;
3428                         }
3429                         if (atoi(p) == 1) {
3430                           Colorize(ColorChannel1, FALSE);
3431                           curColor = ColorChannel1;
3432                         } else {
3433                           Colorize(ColorChannel, FALSE);
3434                           curColor = ColorChannel;
3435                         }
3436                         break;
3437                       case 5:
3438                         curColor = ColorNormal;
3439                         break;
3440                       }
3441                     }
3442                     if (started == STARTED_NONE && appData.autoComment &&
3443                         (gameMode == IcsObserving ||
3444                          gameMode == IcsPlayingWhite ||
3445                          gameMode == IcsPlayingBlack)) {
3446                       parse_pos = i - oldi;
3447                       memcpy(parse, &buf[oldi], parse_pos);
3448                       parse[parse_pos] = NULLCHAR;
3449                       started = STARTED_COMMENT;
3450                       savingComment = TRUE;
3451                     } else if(collective != 3) {
3452                       started = STARTED_CHATTER;
3453                       savingComment = FALSE;
3454                     }
3455                     loggedOn = TRUE;
3456                     continue;
3457                   }
3458                 }
3459
3460                 if (looking_at(buf, &i, "* s-shouts: ") ||
3461                     looking_at(buf, &i, "* c-shouts: ")) {
3462                     if (appData.colorize) {
3463                         if (oldi > next_out) {
3464                             SendToPlayer(&buf[next_out], oldi - next_out);
3465                             next_out = oldi;
3466                         }
3467                         Colorize(ColorSShout, FALSE);
3468                         curColor = ColorSShout;
3469                     }
3470                     loggedOn = TRUE;
3471                     started = STARTED_CHATTER;
3472                     continue;
3473                 }
3474
3475                 if (looking_at(buf, &i, "--->")) {
3476                     loggedOn = TRUE;
3477                     continue;
3478                 }
3479
3480                 if (looking_at(buf, &i, "* shouts: ") ||
3481                     looking_at(buf, &i, "--> ")) {
3482                     if (appData.colorize) {
3483                         if (oldi > next_out) {
3484                             SendToPlayer(&buf[next_out], oldi - next_out);
3485                             next_out = oldi;
3486                         }
3487                         Colorize(ColorShout, FALSE);
3488                         curColor = ColorShout;
3489                     }
3490                     loggedOn = TRUE;
3491                     started = STARTED_CHATTER;
3492                     continue;
3493                 }
3494
3495                 if (looking_at( buf, &i, "Challenge:")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorChallenge, FALSE);
3502                         curColor = ColorChallenge;
3503                     }
3504                     loggedOn = TRUE;
3505                     continue;
3506                 }
3507
3508                 if (looking_at(buf, &i, "* offers you") ||
3509                     looking_at(buf, &i, "* offers to be") ||
3510                     looking_at(buf, &i, "* would like to") ||
3511                     looking_at(buf, &i, "* requests to") ||
3512                     looking_at(buf, &i, "Your opponent offers") ||
3513                     looking_at(buf, &i, "Your opponent requests")) {
3514
3515                     if (appData.colorize) {
3516                         if (oldi > next_out) {
3517                             SendToPlayer(&buf[next_out], oldi - next_out);
3518                             next_out = oldi;
3519                         }
3520                         Colorize(ColorRequest, FALSE);
3521                         curColor = ColorRequest;
3522                     }
3523                     continue;
3524                 }
3525
3526                 if (looking_at(buf, &i, "* (*) seeking")) {
3527                     if (appData.colorize) {
3528                         if (oldi > next_out) {
3529                             SendToPlayer(&buf[next_out], oldi - next_out);
3530                             next_out = oldi;
3531                         }
3532                         Colorize(ColorSeek, FALSE);
3533                         curColor = ColorSeek;
3534                     }
3535                     continue;
3536             }
3537
3538           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3539
3540             if (looking_at(buf, &i, "\\   ")) {
3541                 if (prevColor != ColorNormal) {
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                         next_out = oldi;
3545                     }
3546                     Colorize(prevColor, TRUE);
3547                     curColor = prevColor;
3548                 }
3549                 if (savingComment) {
3550                     parse_pos = i - oldi;
3551                     memcpy(parse, &buf[oldi], parse_pos);
3552                     parse[parse_pos] = NULLCHAR;
3553                     started = STARTED_COMMENT;
3554                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3555                         chattingPartner = savingComment - 3; // kludge to remember the box
3556                 } else {
3557                     started = STARTED_CHATTER;
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "Black Strength :") ||
3563                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3564                 looking_at(buf, &i, "<10>") ||
3565                 looking_at(buf, &i, "#@#")) {
3566                 /* Wrong board style */
3567                 loggedOn = TRUE;
3568                 SendToICS(ics_prefix);
3569                 SendToICS("set style 12\n");
3570                 SendToICS(ics_prefix);
3571                 SendToICS("refresh\n");
3572                 continue;
3573             }
3574
3575             if (looking_at(buf, &i, "login:")) {
3576               if (!have_sent_ICS_logon) {
3577                 if(ICSInitScript())
3578                   have_sent_ICS_logon = 1;
3579                 else // no init script was found
3580                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3581               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3582                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3583               }
3584                 continue;
3585             }
3586
3587             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3588                 (looking_at(buf, &i, "\n<12> ") ||
3589                  looking_at(buf, &i, "<12> "))) {
3590                 loggedOn = TRUE;
3591                 if (oldi > next_out) {
3592                     SendToPlayer(&buf[next_out], oldi - next_out);
3593                 }
3594                 next_out = i;
3595                 started = STARTED_BOARD;
3596                 parse_pos = 0;
3597                 continue;
3598             }
3599
3600             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3601                 looking_at(buf, &i, "<b1> ")) {
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_HOLDINGS;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3612                 loggedOn = TRUE;
3613                 /* Header for a move list -- first line */
3614
3615                 switch (ics_getting_history) {
3616                   case H_FALSE:
3617                     switch (gameMode) {
3618                       case IcsIdle:
3619                       case BeginningOfGame:
3620                         /* User typed "moves" or "oldmoves" while we
3621                            were idle.  Pretend we asked for these
3622                            moves and soak them up so user can step
3623                            through them and/or save them.
3624                            */
3625                         Reset(FALSE, TRUE);
3626                         gameMode = IcsObserving;
3627                         ModeHighlight();
3628                         ics_gamenum = -1;
3629                         ics_getting_history = H_GOT_UNREQ_HEADER;
3630                         break;
3631                       case EditGame: /*?*/
3632                       case EditPosition: /*?*/
3633                         /* Should above feature work in these modes too? */
3634                         /* For now it doesn't */
3635                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3636                         break;
3637                       default:
3638                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3639                         break;
3640                     }
3641                     break;
3642                   case H_REQUESTED:
3643                     /* Is this the right one? */
3644                     if (gameInfo.white && gameInfo.black &&
3645                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3646                         strcmp(gameInfo.black, star_match[2]) == 0) {
3647                         /* All is well */
3648                         ics_getting_history = H_GOT_REQ_HEADER;
3649                     }
3650                     break;
3651                   case H_GOT_REQ_HEADER:
3652                   case H_GOT_UNREQ_HEADER:
3653                   case H_GOT_UNWANTED_HEADER:
3654                   case H_GETTING_MOVES:
3655                     /* Should not happen */
3656                     DisplayError(_("Error gathering move list: two headers"), 0);
3657                     ics_getting_history = H_FALSE;
3658                     break;
3659                 }
3660
3661                 /* Save player ratings into gameInfo if needed */
3662                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3663                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3664                     (gameInfo.whiteRating == -1 ||
3665                      gameInfo.blackRating == -1)) {
3666
3667                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3668                     gameInfo.blackRating = string_to_rating(star_match[3]);
3669                     if (appData.debugMode)
3670                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3671                               gameInfo.whiteRating, gameInfo.blackRating);
3672                 }
3673                 continue;
3674             }
3675
3676             if (looking_at(buf, &i,
3677               "* * match, initial time: * minute*, increment: * second")) {
3678                 /* Header for a move list -- second line */
3679                 /* Initial board will follow if this is a wild game */
3680                 if (gameInfo.event != NULL) free(gameInfo.event);
3681                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3682                 gameInfo.event = StrSave(str);
3683                 /* [HGM] we switched variant. Translate boards if needed. */
3684                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3685                 continue;
3686             }
3687
3688             if (looking_at(buf, &i, "Move  ")) {
3689                 /* Beginning of a move list */
3690                 switch (ics_getting_history) {
3691                   case H_FALSE:
3692                     /* Normally should not happen */
3693                     /* Maybe user hit reset while we were parsing */
3694                     break;
3695                   case H_REQUESTED:
3696                     /* Happens if we are ignoring a move list that is not
3697                      * the one we just requested.  Common if the user
3698                      * tries to observe two games without turning off
3699                      * getMoveList */
3700                     break;
3701                   case H_GETTING_MOVES:
3702                     /* Should not happen */
3703                     DisplayError(_("Error gathering move list: nested"), 0);
3704                     ics_getting_history = H_FALSE;
3705                     break;
3706                   case H_GOT_REQ_HEADER:
3707                     ics_getting_history = H_GETTING_MOVES;
3708                     started = STARTED_MOVES;
3709                     parse_pos = 0;
3710                     if (oldi > next_out) {
3711                         SendToPlayer(&buf[next_out], oldi - next_out);
3712                     }
3713                     break;
3714                   case H_GOT_UNREQ_HEADER:
3715                     ics_getting_history = H_GETTING_MOVES;
3716                     started = STARTED_MOVES_NOHIDE;
3717                     parse_pos = 0;
3718                     break;
3719                   case H_GOT_UNWANTED_HEADER:
3720                     ics_getting_history = H_FALSE;
3721                     break;
3722                 }
3723                 continue;
3724             }
3725
3726             if (looking_at(buf, &i, "% ") ||
3727                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3728                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3729                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3730                     soughtPending = FALSE;
3731                     seekGraphUp = TRUE;
3732                     DrawSeekGraph();
3733                 }
3734                 if(suppressKibitz) next_out = i;
3735                 savingComment = FALSE;
3736                 suppressKibitz = 0;
3737                 switch (started) {
3738                   case STARTED_MOVES:
3739                   case STARTED_MOVES_NOHIDE:
3740                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3741                     parse[parse_pos + i - oldi] = NULLCHAR;
3742                     ParseGameHistory(parse);
3743 #if ZIPPY
3744                     if (appData.zippyPlay && first.initDone) {
3745                         FeedMovesToProgram(&first, forwardMostMove);
3746                         if (gameMode == IcsPlayingWhite) {
3747                             if (WhiteOnMove(forwardMostMove)) {
3748                                 if (first.sendTime) {
3749                                   if (first.useColors) {
3750                                     SendToProgram("black\n", &first);
3751                                   }
3752                                   SendTimeRemaining(&first, TRUE);
3753                                 }
3754                                 if (first.useColors) {
3755                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3756                                 }
3757                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3758                                 first.maybeThinking = TRUE;
3759                             } else {
3760                                 if (first.usePlayother) {
3761                                   if (first.sendTime) {
3762                                     SendTimeRemaining(&first, TRUE);
3763                                   }
3764                                   SendToProgram("playother\n", &first);
3765                                   firstMove = FALSE;
3766                                 } else {
3767                                   firstMove = TRUE;
3768                                 }
3769                             }
3770                         } else if (gameMode == IcsPlayingBlack) {
3771                             if (!WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("white\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, FALSE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("black\n", &first);
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, FALSE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         }
3795                     }
3796 #endif
3797                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3798                         /* Moves came from oldmoves or moves command
3799                            while we weren't doing anything else.
3800                            */
3801                         currentMove = forwardMostMove;
3802                         ClearHighlights();/*!!could figure this out*/
3803                         flipView = appData.flipView;
3804                         DrawPosition(TRUE, boards[currentMove]);
3805                         DisplayBothClocks();
3806                         snprintf(str, MSG_SIZ, "%s %s %s",
3807                                 gameInfo.white, _("vs."),  gameInfo.black);
3808                         DisplayTitle(str);
3809                         gameMode = IcsIdle;
3810                     } else {
3811                         /* Moves were history of an active game */
3812                         if (gameInfo.resultDetails != NULL) {
3813                             free(gameInfo.resultDetails);
3814                             gameInfo.resultDetails = NULL;
3815                         }
3816                     }
3817                     HistorySet(parseList, backwardMostMove,
3818                                forwardMostMove, currentMove-1);
3819                     DisplayMove(currentMove - 1);
3820                     if (started == STARTED_MOVES) next_out = i;
3821                     started = STARTED_NONE;
3822                     ics_getting_history = H_FALSE;
3823                     break;
3824
3825                   case STARTED_OBSERVE:
3826                     started = STARTED_NONE;
3827                     SendToICS(ics_prefix);
3828                     SendToICS("refresh\n");
3829                     break;
3830
3831                   default:
3832                     break;
3833                 }
3834                 if(bookHit) { // [HGM] book: simulate book reply
3835                     static char bookMove[MSG_SIZ]; // a bit generous?
3836
3837                     programStats.nodes = programStats.depth = programStats.time =
3838                     programStats.score = programStats.got_only_move = 0;
3839                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3840
3841                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3842                     strcat(bookMove, bookHit);
3843                     HandleMachineMove(bookMove, &first);
3844                 }
3845                 continue;
3846             }
3847
3848             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3849                  started == STARTED_HOLDINGS ||
3850                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3851                 /* Accumulate characters in move list or board */
3852                 parse[parse_pos++] = buf[i];
3853             }
3854
3855             /* Start of game messages.  Mostly we detect start of game
3856                when the first board image arrives.  On some versions
3857                of the ICS, though, we need to do a "refresh" after starting
3858                to observe in order to get the current board right away. */
3859             if (looking_at(buf, &i, "Adding game * to observation list")) {
3860                 started = STARTED_OBSERVE;
3861                 continue;
3862             }
3863
3864             /* Handle auto-observe */
3865             if (appData.autoObserve &&
3866                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3867                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3868                 char *player;
3869                 /* Choose the player that was highlighted, if any. */
3870                 if (star_match[0][0] == '\033' ||
3871                     star_match[1][0] != '\033') {
3872                     player = star_match[0];
3873                 } else {
3874                     player = star_match[2];
3875                 }
3876                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3877                         ics_prefix, StripHighlightAndTitle(player));
3878                 SendToICS(str);
3879
3880                 /* Save ratings from notify string */
3881                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3882                 player1Rating = string_to_rating(star_match[1]);
3883                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3884                 player2Rating = string_to_rating(star_match[3]);
3885
3886                 if (appData.debugMode)
3887                   fprintf(debugFP,
3888                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3889                           player1Name, player1Rating,
3890                           player2Name, player2Rating);
3891
3892                 continue;
3893             }
3894
3895             /* Deal with automatic examine mode after a game,
3896                and with IcsObserving -> IcsExamining transition */
3897             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3898                 looking_at(buf, &i, "has made you an examiner of game *")) {
3899
3900                 int gamenum = atoi(star_match[0]);
3901                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3902                     gamenum == ics_gamenum) {
3903                     /* We were already playing or observing this game;
3904                        no need to refetch history */
3905                     gameMode = IcsExamining;
3906                     if (pausing) {
3907                         pauseExamForwardMostMove = forwardMostMove;
3908                     } else if (currentMove < forwardMostMove) {
3909                         ForwardInner(forwardMostMove);
3910                     }
3911                 } else {
3912                     /* I don't think this case really can happen */
3913                     SendToICS(ics_prefix);
3914                     SendToICS("refresh\n");
3915                 }
3916                 continue;
3917             }
3918
3919             /* Error messages */
3920 //          if (ics_user_moved) {
3921             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3922                 if (looking_at(buf, &i, "Illegal move") ||
3923                     looking_at(buf, &i, "Not a legal move") ||
3924                     looking_at(buf, &i, "Your king is in check") ||
3925                     looking_at(buf, &i, "It isn't your turn") ||
3926                     looking_at(buf, &i, "It is not your move")) {
3927                     /* Illegal move */
3928                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3929                         currentMove = forwardMostMove-1;
3930                         DisplayMove(currentMove - 1); /* before DMError */
3931                         DrawPosition(FALSE, boards[currentMove]);
3932                         SwitchClocks(forwardMostMove-1); // [HGM] race
3933                         DisplayBothClocks();
3934                     }
3935                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3936                     ics_user_moved = 0;
3937                     continue;
3938                 }
3939             }
3940
3941             if (looking_at(buf, &i, "still have time") ||
3942                 looking_at(buf, &i, "not out of time") ||
3943                 looking_at(buf, &i, "either player is out of time") ||
3944                 looking_at(buf, &i, "has timeseal; checking")) {
3945                 /* We must have called his flag a little too soon */
3946                 whiteFlag = blackFlag = FALSE;
3947                 continue;
3948             }
3949
3950             if (looking_at(buf, &i, "added * seconds to") ||
3951                 looking_at(buf, &i, "seconds were added to")) {
3952                 /* Update the clocks */
3953                 SendToICS(ics_prefix);
3954                 SendToICS("refresh\n");
3955                 continue;
3956             }
3957
3958             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3959                 ics_clock_paused = TRUE;
3960                 StopClocks();
3961                 continue;
3962             }
3963
3964             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3965                 ics_clock_paused = FALSE;
3966                 StartClocks();
3967                 continue;
3968             }
3969
3970             /* Grab player ratings from the Creating: message.
3971                Note we have to check for the special case when
3972                the ICS inserts things like [white] or [black]. */
3973             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3974                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3975                 /* star_matches:
3976                    0    player 1 name (not necessarily white)
3977                    1    player 1 rating
3978                    2    empty, white, or black (IGNORED)
3979                    3    player 2 name (not necessarily black)
3980                    4    player 2 rating
3981
3982                    The names/ratings are sorted out when the game
3983                    actually starts (below).
3984                 */
3985                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3986                 player1Rating = string_to_rating(star_match[1]);
3987                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3988                 player2Rating = string_to_rating(star_match[4]);
3989
3990                 if (appData.debugMode)
3991                   fprintf(debugFP,
3992                           "Ratings from 'Creating:' %s %d, %s %d\n",
3993                           player1Name, player1Rating,
3994                           player2Name, player2Rating);
3995
3996                 continue;
3997             }
3998
3999             /* Improved generic start/end-of-game messages */
4000             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4001                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4002                 /* If tkind == 0: */
4003                 /* star_match[0] is the game number */
4004                 /*           [1] is the white player's name */
4005                 /*           [2] is the black player's name */
4006                 /* For end-of-game: */
4007                 /*           [3] is the reason for the game end */
4008                 /*           [4] is a PGN end game-token, preceded by " " */
4009                 /* For start-of-game: */
4010                 /*           [3] begins with "Creating" or "Continuing" */
4011                 /*           [4] is " *" or empty (don't care). */
4012                 int gamenum = atoi(star_match[0]);
4013                 char *whitename, *blackname, *why, *endtoken;
4014                 ChessMove endtype = EndOfFile;
4015
4016                 if (tkind == 0) {
4017                   whitename = star_match[1];
4018                   blackname = star_match[2];
4019                   why = star_match[3];
4020                   endtoken = star_match[4];
4021                 } else {
4022                   whitename = star_match[1];
4023                   blackname = star_match[3];
4024                   why = star_match[5];
4025                   endtoken = star_match[6];
4026                 }
4027
4028                 /* Game start messages */
4029                 if (strncmp(why, "Creating ", 9) == 0 ||
4030                     strncmp(why, "Continuing ", 11) == 0) {
4031                     gs_gamenum = gamenum;
4032                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4033                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4034                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4035 #if ZIPPY
4036                     if (appData.zippyPlay) {
4037                         ZippyGameStart(whitename, blackname);
4038                     }
4039 #endif /*ZIPPY*/
4040                     partnerBoardValid = FALSE; // [HGM] bughouse
4041                     continue;
4042                 }
4043
4044                 /* Game end messages */
4045                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4046                     ics_gamenum != gamenum) {
4047                     continue;
4048                 }
4049                 while (endtoken[0] == ' ') endtoken++;
4050                 switch (endtoken[0]) {
4051                   case '*':
4052                   default:
4053                     endtype = GameUnfinished;
4054                     break;
4055                   case '0':
4056                     endtype = BlackWins;
4057                     break;
4058                   case '1':
4059                     if (endtoken[1] == '/')
4060                       endtype = GameIsDrawn;
4061                     else
4062                       endtype = WhiteWins;
4063                     break;
4064                 }
4065                 GameEnds(endtype, why, GE_ICS);
4066 #if ZIPPY
4067                 if (appData.zippyPlay && first.initDone) {
4068                     ZippyGameEnd(endtype, why);
4069                     if (first.pr == NoProc) {
4070                       /* Start the next process early so that we'll
4071                          be ready for the next challenge */
4072                       StartChessProgram(&first);
4073                     }
4074                     /* Send "new" early, in case this command takes
4075                        a long time to finish, so that we'll be ready
4076                        for the next challenge. */
4077                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4078                     Reset(TRUE, TRUE);
4079                 }
4080 #endif /*ZIPPY*/
4081                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4082                 continue;
4083             }
4084
4085             if (looking_at(buf, &i, "Removing game * from observation") ||
4086                 looking_at(buf, &i, "no longer observing game *") ||
4087                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4088                 if (gameMode == IcsObserving &&
4089                     atoi(star_match[0]) == ics_gamenum)
4090                   {
4091                       /* icsEngineAnalyze */
4092                       if (appData.icsEngineAnalyze) {
4093                             ExitAnalyzeMode();
4094                             ModeHighlight();
4095                       }
4096                       StopClocks();
4097                       gameMode = IcsIdle;
4098                       ics_gamenum = -1;
4099                       ics_user_moved = FALSE;
4100                   }
4101                 continue;
4102             }
4103
4104             if (looking_at(buf, &i, "no longer examining game *")) {
4105                 if (gameMode == IcsExamining &&
4106                     atoi(star_match[0]) == ics_gamenum)
4107                   {
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             /* Advance leftover_start past any newlines we find,
4116                so only partial lines can get reparsed */
4117             if (looking_at(buf, &i, "\n")) {
4118                 prevColor = curColor;
4119                 if (curColor != ColorNormal) {
4120                     if (oldi > next_out) {
4121                         SendToPlayer(&buf[next_out], oldi - next_out);
4122                         next_out = oldi;
4123                     }
4124                     Colorize(ColorNormal, FALSE);
4125                     curColor = ColorNormal;
4126                 }
4127                 if (started == STARTED_BOARD) {
4128                     started = STARTED_NONE;
4129                     parse[parse_pos] = NULLCHAR;
4130                     ParseBoard12(parse);
4131                     ics_user_moved = 0;
4132
4133                     /* Send premove here */
4134                     if (appData.premove) {
4135                       char str[MSG_SIZ];
4136                       if (currentMove == 0 &&
4137                           gameMode == IcsPlayingWhite &&
4138                           appData.premoveWhite) {
4139                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4140                         if (appData.debugMode)
4141                           fprintf(debugFP, "Sending premove:\n");
4142                         SendToICS(str);
4143                       } else if (currentMove == 1 &&
4144                                  gameMode == IcsPlayingBlack &&
4145                                  appData.premoveBlack) {
4146                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                         SendToICS(str);
4150                       } else if (gotPremove) {
4151                         gotPremove = 0;
4152                         ClearPremoveHighlights();
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                           UserMoveEvent(premoveFromX, premoveFromY,
4156                                         premoveToX, premoveToY,
4157                                         premovePromoChar);
4158                       }
4159                     }
4160
4161                     /* Usually suppress following prompt */
4162                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4163                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4164                         if (looking_at(buf, &i, "*% ")) {
4165                             savingComment = FALSE;
4166                             suppressKibitz = 0;
4167                         }
4168                     }
4169                     next_out = i;
4170                 } else if (started == STARTED_HOLDINGS) {
4171                     int gamenum;
4172                     char new_piece[MSG_SIZ];
4173                     started = STARTED_NONE;
4174                     parse[parse_pos] = NULLCHAR;
4175                     if (appData.debugMode)
4176                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4177                                                         parse, currentMove);
4178                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4179                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4180                         if (gameInfo.variant == VariantNormal) {
4181                           /* [HGM] We seem to switch variant during a game!
4182                            * Presumably no holdings were displayed, so we have
4183                            * to move the position two files to the right to
4184                            * create room for them!
4185                            */
4186                           VariantClass newVariant;
4187                           switch(gameInfo.boardWidth) { // base guess on board width
4188                                 case 9:  newVariant = VariantShogi; break;
4189                                 case 10: newVariant = VariantGreat; break;
4190                                 default: newVariant = VariantCrazyhouse; break;
4191                           }
4192                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4193                           /* Get a move list just to see the header, which
4194                              will tell us whether this is really bug or zh */
4195                           if (ics_getting_history == H_FALSE) {
4196                             ics_getting_history = H_REQUESTED;
4197                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4198                             SendToICS(str);
4199                           }
4200                         }
4201                         new_piece[0] = NULLCHAR;
4202                         sscanf(parse, "game %d white [%s black [%s <- %s",
4203                                &gamenum, white_holding, black_holding,
4204                                new_piece);
4205                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4206                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4207                         /* [HGM] copy holdings to board holdings area */
4208                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4209                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4210                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4211 #if ZIPPY
4212                         if (appData.zippyPlay && first.initDone) {
4213                             ZippyHoldings(white_holding, black_holding,
4214                                           new_piece);
4215                         }
4216 #endif /*ZIPPY*/
4217                         if (tinyLayout || smallLayout) {
4218                             char wh[16], bh[16];
4219                             PackHolding(wh, white_holding);
4220                             PackHolding(bh, black_holding);
4221                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4222                                     gameInfo.white, gameInfo.black);
4223                         } else {
4224                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4225                                     gameInfo.white, white_holding, _("vs."),
4226                                     gameInfo.black, black_holding);
4227                         }
4228                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4229                         DrawPosition(FALSE, boards[currentMove]);
4230                         DisplayTitle(str);
4231                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         /* [HGM] copy holdings to partner-board holdings area */
4238                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4239                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4240                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4241                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4243                       }
4244                     }
4245                     /* Suppress following prompt */
4246                     if (looking_at(buf, &i, "*% ")) {
4247                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4248                         savingComment = FALSE;
4249                         suppressKibitz = 0;
4250                     }
4251                     next_out = i;
4252                 }
4253                 continue;
4254             }
4255
4256             i++;                /* skip unparsed character and loop back */
4257         }
4258
4259         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4260 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4261 //          SendToPlayer(&buf[next_out], i - next_out);
4262             started != STARTED_HOLDINGS && leftover_start > next_out) {
4263             SendToPlayer(&buf[next_out], leftover_start - next_out);
4264             next_out = i;
4265         }
4266
4267         leftover_len = buf_len - leftover_start;
4268         /* if buffer ends with something we couldn't parse,
4269            reparse it after appending the next read */
4270
4271     } else if (count == 0) {
4272         RemoveInputSource(isr);
4273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4274     } else {
4275         DisplayFatalError(_("Error reading from ICS"), error, 1);
4276     }
4277 }
4278
4279
4280 /* Board style 12 looks like this:
4281
4282    <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
4283
4284  * The "<12> " is stripped before it gets to this routine.  The two
4285  * trailing 0's (flip state and clock ticking) are later addition, and
4286  * some chess servers may not have them, or may have only the first.
4287  * Additional trailing fields may be added in the future.
4288  */
4289
4290 #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"
4291
4292 #define RELATION_OBSERVING_PLAYED    0
4293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4294 #define RELATION_PLAYING_MYMOVE      1
4295 #define RELATION_PLAYING_NOTMYMOVE  -1
4296 #define RELATION_EXAMINING           2
4297 #define RELATION_ISOLATED_BOARD     -3
4298 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4299
4300 void
4301 ParseBoard12 (char *string)
4302 {
4303 #if ZIPPY
4304     int i, takeback;
4305     char *bookHit = NULL; // [HGM] book
4306 #endif
4307     GameMode newGameMode;
4308     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4309     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4310     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4311     char to_play, board_chars[200];
4312     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4313     char black[32], white[32];
4314     Board board;
4315     int prevMove = currentMove;
4316     int ticking = 2;
4317     ChessMove moveType;
4318     int fromX, fromY, toX, toY;
4319     char promoChar;
4320     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4321     Boolean weird = FALSE, reqFlag = FALSE;
4322
4323     fromX = fromY = toX = toY = -1;
4324
4325     newGame = FALSE;
4326
4327     if (appData.debugMode)
4328       fprintf(debugFP, "Parsing board: %s\n", string);
4329
4330     move_str[0] = NULLCHAR;
4331     elapsed_time[0] = NULLCHAR;
4332     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4333         int  i = 0, j;
4334         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4335             if(string[i] == ' ') { ranks++; files = 0; }
4336             else files++;
4337             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4338             i++;
4339         }
4340         for(j = 0; j <i; j++) board_chars[j] = string[j];
4341         board_chars[i] = '\0';
4342         string += i + 1;
4343     }
4344     n = sscanf(string, PATTERN, &to_play, &double_push,
4345                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4346                &gamenum, white, black, &relation, &basetime, &increment,
4347                &white_stren, &black_stren, &white_time, &black_time,
4348                &moveNum, str, elapsed_time, move_str, &ics_flip,
4349                &ticking);
4350
4351     if (n < 21) {
4352         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4353         DisplayError(str, 0);
4354         return;
4355     }
4356
4357     /* Convert the move number to internal form */
4358     moveNum = (moveNum - 1) * 2;
4359     if (to_play == 'B') moveNum++;
4360     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4361       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4362                         0, 1);
4363       return;
4364     }
4365
4366     switch (relation) {
4367       case RELATION_OBSERVING_PLAYED:
4368       case RELATION_OBSERVING_STATIC:
4369         if (gamenum == -1) {
4370             /* Old ICC buglet */
4371             relation = RELATION_OBSERVING_STATIC;
4372         }
4373         newGameMode = IcsObserving;
4374         break;
4375       case RELATION_PLAYING_MYMOVE:
4376       case RELATION_PLAYING_NOTMYMOVE:
4377         newGameMode =
4378           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4379             IcsPlayingWhite : IcsPlayingBlack;
4380         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4381         break;
4382       case RELATION_EXAMINING:
4383         newGameMode = IcsExamining;
4384         break;
4385       case RELATION_ISOLATED_BOARD:
4386       default:
4387         /* Just display this board.  If user was doing something else,
4388            we will forget about it until the next board comes. */
4389         newGameMode = IcsIdle;
4390         break;
4391       case RELATION_STARTING_POSITION:
4392         newGameMode = gameMode;
4393         break;
4394     }
4395
4396     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4397         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4398          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4399       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4400       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4401       static int lastBgGame = -1;
4402       char *toSqr;
4403       for (k = 0; k < ranks; k++) {
4404         for (j = 0; j < files; j++)
4405           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4406         if(gameInfo.holdingsWidth > 1) {
4407              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4408              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4409         }
4410       }
4411       CopyBoard(partnerBoard, board);
4412       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4413         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4414         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4415       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4416       if(toSqr = strchr(str, '-')) {
4417         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4418         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4419       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4420       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4421       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4422       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4423       if(twoBoards) {
4424           DisplayWhiteClock(white_time*fac, to_play == 'W');
4425           DisplayBlackClock(black_time*fac, to_play != 'W');
4426           activePartner = to_play;
4427           if(gamenum != lastBgGame) {
4428               char buf[MSG_SIZ];
4429               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4430               DisplayTitle(buf);
4431           }
4432           lastBgGame = gamenum;
4433           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4434                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4435       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4436                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4437       if(!twoBoards) DisplayMessage(partnerStatus, "");
4438         partnerBoardValid = TRUE;
4439       return;
4440     }
4441
4442     if(appData.dualBoard && appData.bgObserve) {
4443         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4444             SendToICS(ics_prefix), SendToICS("pobserve\n");
4445         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4446             char buf[MSG_SIZ];
4447             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4448             SendToICS(buf);
4449         }
4450     }
4451
4452     /* Modify behavior for initial board display on move listing
4453        of wild games.
4454        */
4455     switch (ics_getting_history) {
4456       case H_FALSE:
4457       case H_REQUESTED:
4458         break;
4459       case H_GOT_REQ_HEADER:
4460       case H_GOT_UNREQ_HEADER:
4461         /* This is the initial position of the current game */
4462         gamenum = ics_gamenum;
4463         moveNum = 0;            /* old ICS bug workaround */
4464         if (to_play == 'B') {
4465           startedFromSetupPosition = TRUE;
4466           blackPlaysFirst = TRUE;
4467           moveNum = 1;
4468           if (forwardMostMove == 0) forwardMostMove = 1;
4469           if (backwardMostMove == 0) backwardMostMove = 1;
4470           if (currentMove == 0) currentMove = 1;
4471         }
4472         newGameMode = gameMode;
4473         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4474         break;
4475       case H_GOT_UNWANTED_HEADER:
4476         /* This is an initial board that we don't want */
4477         return;
4478       case H_GETTING_MOVES:
4479         /* Should not happen */
4480         DisplayError(_("Error gathering move list: extra board"), 0);
4481         ics_getting_history = H_FALSE;
4482         return;
4483     }
4484
4485    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4486                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4487                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4488      /* [HGM] We seem to have switched variant unexpectedly
4489       * Try to guess new variant from board size
4490       */
4491           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4492           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4493           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4494           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4495           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4496           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4497           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4498           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4499           /* Get a move list just to see the header, which
4500              will tell us whether this is really bug or zh */
4501           if (ics_getting_history == H_FALSE) {
4502             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4503             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4504             SendToICS(str);
4505           }
4506     }
4507
4508     /* Take action if this is the first board of a new game, or of a
4509        different game than is currently being displayed.  */
4510     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4511         relation == RELATION_ISOLATED_BOARD) {
4512
4513         /* Forget the old game and get the history (if any) of the new one */
4514         if (gameMode != BeginningOfGame) {
4515           Reset(TRUE, TRUE);
4516         }
4517         newGame = TRUE;
4518         if (appData.autoRaiseBoard) BoardToTop();
4519         prevMove = -3;
4520         if (gamenum == -1) {
4521             newGameMode = IcsIdle;
4522         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4523                    appData.getMoveList && !reqFlag) {
4524             /* Need to get game history */
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529
4530         /* Initially flip the board to have black on the bottom if playing
4531            black or if the ICS flip flag is set, but let the user change
4532            it with the Flip View button. */
4533         flipView = appData.autoFlipView ?
4534           (newGameMode == IcsPlayingBlack) || ics_flip :
4535           appData.flipView;
4536
4537         /* Done with values from previous mode; copy in new ones */
4538         gameMode = newGameMode;
4539         ModeHighlight();
4540         ics_gamenum = gamenum;
4541         if (gamenum == gs_gamenum) {
4542             int klen = strlen(gs_kind);
4543             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4544             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4545             gameInfo.event = StrSave(str);
4546         } else {
4547             gameInfo.event = StrSave("ICS game");
4548         }
4549         gameInfo.site = StrSave(appData.icsHost);
4550         gameInfo.date = PGNDate();
4551         gameInfo.round = StrSave("-");
4552         gameInfo.white = StrSave(white);
4553         gameInfo.black = StrSave(black);
4554         timeControl = basetime * 60 * 1000;
4555         timeControl_2 = 0;
4556         timeIncrement = increment * 1000;
4557         movesPerSession = 0;
4558         gameInfo.timeControl = TimeControlTagValue();
4559         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4560   if (appData.debugMode) {
4561     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4562     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4563     setbuf(debugFP, NULL);
4564   }
4565
4566         gameInfo.outOfBook = NULL;
4567
4568         /* Do we have the ratings? */
4569         if (strcmp(player1Name, white) == 0 &&
4570             strcmp(player2Name, black) == 0) {
4571             if (appData.debugMode)
4572               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4573                       player1Rating, player2Rating);
4574             gameInfo.whiteRating = player1Rating;
4575             gameInfo.blackRating = player2Rating;
4576         } else if (strcmp(player2Name, white) == 0 &&
4577                    strcmp(player1Name, black) == 0) {
4578             if (appData.debugMode)
4579               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4580                       player2Rating, player1Rating);
4581             gameInfo.whiteRating = player2Rating;
4582             gameInfo.blackRating = player1Rating;
4583         }
4584         player1Name[0] = player2Name[0] = NULLCHAR;
4585
4586         /* Silence shouts if requested */
4587         if (appData.quietPlay &&
4588             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4589             SendToICS(ics_prefix);
4590             SendToICS("set shout 0\n");
4591         }
4592     }
4593
4594     /* Deal with midgame name changes */
4595     if (!newGame) {
4596         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4597             if (gameInfo.white) free(gameInfo.white);
4598             gameInfo.white = StrSave(white);
4599         }
4600         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4601             if (gameInfo.black) free(gameInfo.black);
4602             gameInfo.black = StrSave(black);
4603         }
4604     }
4605
4606     /* Throw away game result if anything actually changes in examine mode */
4607     if (gameMode == IcsExamining && !newGame) {
4608         gameInfo.result = GameUnfinished;
4609         if (gameInfo.resultDetails != NULL) {
4610             free(gameInfo.resultDetails);
4611             gameInfo.resultDetails = NULL;
4612         }
4613     }
4614
4615     /* In pausing && IcsExamining mode, we ignore boards coming
4616        in if they are in a different variation than we are. */
4617     if (pauseExamInvalid) return;
4618     if (pausing && gameMode == IcsExamining) {
4619         if (moveNum <= pauseExamForwardMostMove) {
4620             pauseExamInvalid = TRUE;
4621             forwardMostMove = pauseExamForwardMostMove;
4622             return;
4623         }
4624     }
4625
4626   if (appData.debugMode) {
4627     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4628   }
4629     /* Parse the board */
4630     for (k = 0; k < ranks; k++) {
4631       for (j = 0; j < files; j++)
4632         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4633       if(gameInfo.holdingsWidth > 1) {
4634            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4635            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4636       }
4637     }
4638     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4639       board[5][BOARD_RGHT+1] = WhiteAngel;
4640       board[6][BOARD_RGHT+1] = WhiteMarshall;
4641       board[1][0] = BlackMarshall;
4642       board[2][0] = BlackAngel;
4643       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4644     }
4645     CopyBoard(boards[moveNum], board);
4646     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4647     if (moveNum == 0) {
4648         startedFromSetupPosition =
4649           !CompareBoards(board, initialPosition);
4650         if(startedFromSetupPosition)
4651             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4652     }
4653
4654     /* [HGM] Set castling rights. Take the outermost Rooks,
4655        to make it also work for FRC opening positions. Note that board12
4656        is really defective for later FRC positions, as it has no way to
4657        indicate which Rook can castle if they are on the same side of King.
4658        For the initial position we grant rights to the outermost Rooks,
4659        and remember thos rights, and we then copy them on positions
4660        later in an FRC game. This means WB might not recognize castlings with
4661        Rooks that have moved back to their original position as illegal,
4662        but in ICS mode that is not its job anyway.
4663     */
4664     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4665     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4666
4667         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4668             if(board[0][i] == WhiteRook) j = i;
4669         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4670         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4671             if(board[0][i] == WhiteRook) j = i;
4672         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4673         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4674             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4675         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4676         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4677             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4678         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4679
4680         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4681         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4682         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4683             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4684         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4685             if(board[BOARD_HEIGHT-1][k] == bKing)
4686                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4687         if(gameInfo.variant == VariantTwoKings) {
4688             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4689             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4690             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4691         }
4692     } else { int r;
4693         r = boards[moveNum][CASTLING][0] = initialRights[0];
4694         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4695         r = boards[moveNum][CASTLING][1] = initialRights[1];
4696         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4697         r = boards[moveNum][CASTLING][3] = initialRights[3];
4698         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4699         r = boards[moveNum][CASTLING][4] = initialRights[4];
4700         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4701         /* wildcastle kludge: always assume King has rights */
4702         r = boards[moveNum][CASTLING][2] = initialRights[2];
4703         r = boards[moveNum][CASTLING][5] = initialRights[5];
4704     }
4705     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4706     boards[moveNum][EP_STATUS] = EP_NONE;
4707     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4708     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4709     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4710
4711
4712     if (ics_getting_history == H_GOT_REQ_HEADER ||
4713         ics_getting_history == H_GOT_UNREQ_HEADER) {
4714         /* This was an initial position from a move list, not
4715            the current position */
4716         return;
4717     }
4718
4719     /* Update currentMove and known move number limits */
4720     newMove = newGame || moveNum > forwardMostMove;
4721
4722     if (newGame) {
4723         forwardMostMove = backwardMostMove = currentMove = moveNum;
4724         if (gameMode == IcsExamining && moveNum == 0) {
4725           /* Workaround for ICS limitation: we are not told the wild
4726              type when starting to examine a game.  But if we ask for
4727              the move list, the move list header will tell us */
4728             ics_getting_history = H_REQUESTED;
4729             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4730             SendToICS(str);
4731         }
4732     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4733                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4734 #if ZIPPY
4735         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4736         /* [HGM] applied this also to an engine that is silently watching        */
4737         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4738             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4739             gameInfo.variant == currentlyInitializedVariant) {
4740           takeback = forwardMostMove - moveNum;
4741           for (i = 0; i < takeback; i++) {
4742             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4743             SendToProgram("undo\n", &first);
4744           }
4745         }
4746 #endif
4747
4748         forwardMostMove = moveNum;
4749         if (!pausing || currentMove > forwardMostMove)
4750           currentMove = forwardMostMove;
4751     } else {
4752         /* New part of history that is not contiguous with old part */
4753         if (pausing && gameMode == IcsExamining) {
4754             pauseExamInvalid = TRUE;
4755             forwardMostMove = pauseExamForwardMostMove;
4756             return;
4757         }
4758         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4759 #if ZIPPY
4760             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4761                 // [HGM] when we will receive the move list we now request, it will be
4762                 // fed to the engine from the first move on. So if the engine is not
4763                 // in the initial position now, bring it there.
4764                 InitChessProgram(&first, 0);
4765             }
4766 #endif
4767             ics_getting_history = H_REQUESTED;
4768             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4769             SendToICS(str);
4770         }
4771         forwardMostMove = backwardMostMove = currentMove = moveNum;
4772     }
4773
4774     /* Update the clocks */
4775     if (strchr(elapsed_time, '.')) {
4776       /* Time is in ms */
4777       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4778       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4779     } else {
4780       /* Time is in seconds */
4781       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4782       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4783     }
4784
4785
4786 #if ZIPPY
4787     if (appData.zippyPlay && newGame &&
4788         gameMode != IcsObserving && gameMode != IcsIdle &&
4789         gameMode != IcsExamining)
4790       ZippyFirstBoard(moveNum, basetime, increment);
4791 #endif
4792
4793     /* Put the move on the move list, first converting
4794        to canonical algebraic form. */
4795     if (moveNum > 0) {
4796   if (appData.debugMode) {
4797     int f = forwardMostMove;
4798     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4799             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4800             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4801     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4802     fprintf(debugFP, "moveNum = %d\n", moveNum);
4803     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4804     setbuf(debugFP, NULL);
4805   }
4806         if (moveNum <= backwardMostMove) {
4807             /* We don't know what the board looked like before
4808                this move.  Punt. */
4809           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4810             strcat(parseList[moveNum - 1], " ");
4811             strcat(parseList[moveNum - 1], elapsed_time);
4812             moveList[moveNum - 1][0] = NULLCHAR;
4813         } else if (strcmp(move_str, "none") == 0) {
4814             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4815             /* Again, we don't know what the board looked like;
4816                this is really the start of the game. */
4817             parseList[moveNum - 1][0] = NULLCHAR;
4818             moveList[moveNum - 1][0] = NULLCHAR;
4819             backwardMostMove = moveNum;
4820             startedFromSetupPosition = TRUE;
4821             fromX = fromY = toX = toY = -1;
4822         } else {
4823           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4824           //                 So we parse the long-algebraic move string in stead of the SAN move
4825           int valid; char buf[MSG_SIZ], *prom;
4826
4827           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4828                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4829           // str looks something like "Q/a1-a2"; kill the slash
4830           if(str[1] == '/')
4831             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4832           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4833           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4834                 strcat(buf, prom); // long move lacks promo specification!
4835           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4836                 if(appData.debugMode)
4837                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4838                 safeStrCpy(move_str, buf, MSG_SIZ);
4839           }
4840           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4841                                 &fromX, &fromY, &toX, &toY, &promoChar)
4842                || ParseOneMove(buf, moveNum - 1, &moveType,
4843                                 &fromX, &fromY, &toX, &toY, &promoChar);
4844           // end of long SAN patch
4845           if (valid) {
4846             (void) CoordsToAlgebraic(boards[moveNum - 1],
4847                                      PosFlags(moveNum - 1),
4848                                      fromY, fromX, toY, toX, promoChar,
4849                                      parseList[moveNum-1]);
4850             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4851               case MT_NONE:
4852               case MT_STALEMATE:
4853               default:
4854                 break;
4855               case MT_CHECK:
4856                 if(!IS_SHOGI(gameInfo.variant))
4857                     strcat(parseList[moveNum - 1], "+");
4858                 break;
4859               case MT_CHECKMATE:
4860               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4861                 strcat(parseList[moveNum - 1], "#");
4862                 break;
4863             }
4864             strcat(parseList[moveNum - 1], " ");
4865             strcat(parseList[moveNum - 1], elapsed_time);
4866             /* currentMoveString is set as a side-effect of ParseOneMove */
4867             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4868             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4869             strcat(moveList[moveNum - 1], "\n");
4870
4871             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4872                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4873               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4874                 ChessSquare old, new = boards[moveNum][k][j];
4875                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4876                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4877                   if(old == new) continue;
4878                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4879                   else if(new == WhiteWazir || new == BlackWazir) {
4880                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4881                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4882                       else boards[moveNum][k][j] = old; // preserve type of Gold
4883                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4884                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4885               }
4886           } else {
4887             /* Move from ICS was illegal!?  Punt. */
4888             if (appData.debugMode) {
4889               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4890               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4891             }
4892             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4893             strcat(parseList[moveNum - 1], " ");
4894             strcat(parseList[moveNum - 1], elapsed_time);
4895             moveList[moveNum - 1][0] = NULLCHAR;
4896             fromX = fromY = toX = toY = -1;
4897           }
4898         }
4899   if (appData.debugMode) {
4900     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4901     setbuf(debugFP, NULL);
4902   }
4903
4904 #if ZIPPY
4905         /* Send move to chess program (BEFORE animating it). */
4906         if (appData.zippyPlay && !newGame && newMove &&
4907            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4908
4909             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4910                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4911                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4912                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4913                             move_str);
4914                     DisplayError(str, 0);
4915                 } else {
4916                     if (first.sendTime) {
4917                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4918                     }
4919                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4920                     if (firstMove && !bookHit) {
4921                         firstMove = FALSE;
4922                         if (first.useColors) {
4923                           SendToProgram(gameMode == IcsPlayingWhite ?
4924                                         "white\ngo\n" :
4925                                         "black\ngo\n", &first);
4926                         } else {
4927                           SendToProgram("go\n", &first);
4928                         }
4929                         first.maybeThinking = TRUE;
4930                     }
4931                 }
4932             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4933               if (moveList[moveNum - 1][0] == NULLCHAR) {
4934                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4935                 DisplayError(str, 0);
4936               } else {
4937                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4938                 SendMoveToProgram(moveNum - 1, &first);
4939               }
4940             }
4941         }
4942 #endif
4943     }
4944
4945     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4946         /* If move comes from a remote source, animate it.  If it
4947            isn't remote, it will have already been animated. */
4948         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4949             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4950         }
4951         if (!pausing && appData.highlightLastMove) {
4952             SetHighlights(fromX, fromY, toX, toY);
4953         }
4954     }
4955
4956     /* Start the clocks */
4957     whiteFlag = blackFlag = FALSE;
4958     appData.clockMode = !(basetime == 0 && increment == 0);
4959     if (ticking == 0) {
4960       ics_clock_paused = TRUE;
4961       StopClocks();
4962     } else if (ticking == 1) {
4963       ics_clock_paused = FALSE;
4964     }
4965     if (gameMode == IcsIdle ||
4966         relation == RELATION_OBSERVING_STATIC ||
4967         relation == RELATION_EXAMINING ||
4968         ics_clock_paused)
4969       DisplayBothClocks();
4970     else
4971       StartClocks();
4972
4973     /* Display opponents and material strengths */
4974     if (gameInfo.variant != VariantBughouse &&
4975         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4976         if (tinyLayout || smallLayout) {
4977             if(gameInfo.variant == VariantNormal)
4978               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4980                     basetime, increment);
4981             else
4982               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4984                     basetime, increment, (int) gameInfo.variant);
4985         } else {
4986             if(gameInfo.variant == VariantNormal)
4987               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4988                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4989                     basetime, increment);
4990             else
4991               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4992                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4993                     basetime, increment, VariantName(gameInfo.variant));
4994         }
4995         DisplayTitle(str);
4996   if (appData.debugMode) {
4997     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4998   }
4999     }
5000
5001
5002     /* Display the board */
5003     if (!pausing && !appData.noGUI) {
5004
5005       if (appData.premove)
5006           if (!gotPremove ||
5007              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5008              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5009               ClearPremoveHighlights();
5010
5011       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5012         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5013       DrawPosition(j, boards[currentMove]);
5014
5015       DisplayMove(moveNum - 1);
5016       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5017             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5018               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5019         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5020       }
5021     }
5022
5023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5024 #if ZIPPY
5025     if(bookHit) { // [HGM] book: simulate book reply
5026         static char bookMove[MSG_SIZ]; // a bit generous?
5027
5028         programStats.nodes = programStats.depth = programStats.time =
5029         programStats.score = programStats.got_only_move = 0;
5030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5031
5032         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5033         strcat(bookMove, bookHit);
5034         HandleMachineMove(bookMove, &first);
5035     }
5036 #endif
5037 }
5038
5039 void
5040 GetMoveListEvent ()
5041 {
5042     char buf[MSG_SIZ];
5043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5044         ics_getting_history = H_REQUESTED;
5045         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5046         SendToICS(buf);
5047     }
5048 }
5049
5050 void
5051 SendToBoth (char *msg)
5052 {   // to make it easy to keep two engines in step in dual analysis
5053     SendToProgram(msg, &first);
5054     if(second.analyzing) SendToProgram(msg, &second);
5055 }
5056
5057 void
5058 AnalysisPeriodicEvent (int force)
5059 {
5060     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5061          && !force) || !appData.periodicUpdates)
5062       return;
5063
5064     /* Send . command to Crafty to collect stats */
5065     SendToBoth(".\n");
5066
5067     /* Don't send another until we get a response (this makes
5068        us stop sending to old Crafty's which don't understand
5069        the "." command (sending illegal cmds resets node count & time,
5070        which looks bad)) */
5071     programStats.ok_to_send = 0;
5072 }
5073
5074 void
5075 ics_update_width (int new_width)
5076 {
5077         ics_printf("set width %d\n", new_width);
5078 }
5079
5080 void
5081 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5082 {
5083     char buf[MSG_SIZ];
5084
5085     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5086         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5087             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5088             SendToProgram(buf, cps);
5089             return;
5090         }
5091         // null move in variant where engine does not understand it (for analysis purposes)
5092         SendBoard(cps, moveNum + 1); // send position after move in stead.
5093         return;
5094     }
5095     if (cps->useUsermove) {
5096       SendToProgram("usermove ", cps);
5097     }
5098     if (cps->useSAN) {
5099       char *space;
5100       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5101         int len = space - parseList[moveNum];
5102         memcpy(buf, parseList[moveNum], len);
5103         buf[len++] = '\n';
5104         buf[len] = NULLCHAR;
5105       } else {
5106         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5107       }
5108       SendToProgram(buf, cps);
5109     } else {
5110       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5111         AlphaRank(moveList[moveNum], 4);
5112         SendToProgram(moveList[moveNum], cps);
5113         AlphaRank(moveList[moveNum], 4); // and back
5114       } else
5115       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5116        * the engine. It would be nice to have a better way to identify castle
5117        * moves here. */
5118       if(appData.fischerCastling && cps->useOOCastle) {
5119         int fromX = moveList[moveNum][0] - AAA;
5120         int fromY = moveList[moveNum][1] - ONE;
5121         int toX = moveList[moveNum][2] - AAA;
5122         int toY = moveList[moveNum][3] - ONE;
5123         if((boards[moveNum][fromY][fromX] == WhiteKing
5124             && boards[moveNum][toY][toX] == WhiteRook)
5125            || (boards[moveNum][fromY][fromX] == BlackKing
5126                && boards[moveNum][toY][toX] == BlackRook)) {
5127           if(toX > fromX) SendToProgram("O-O\n", cps);
5128           else SendToProgram("O-O-O\n", cps);
5129         }
5130         else SendToProgram(moveList[moveNum], cps);
5131       } else
5132       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5133           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5134                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5135                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5136                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5137           SendToProgram(buf, cps);
5138       } else
5139       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5140         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5141           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5142           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5143                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5144         } else
5145           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5146                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5147         SendToProgram(buf, cps);
5148       }
5149       else SendToProgram(moveList[moveNum], cps);
5150       /* End of additions by Tord */
5151     }
5152
5153     /* [HGM] setting up the opening has brought engine in force mode! */
5154     /*       Send 'go' if we are in a mode where machine should play. */
5155     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5156         (gameMode == TwoMachinesPlay   ||
5157 #if ZIPPY
5158          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5159 #endif
5160          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5161         SendToProgram("go\n", cps);
5162   if (appData.debugMode) {
5163     fprintf(debugFP, "(extra)\n");
5164   }
5165     }
5166     setboardSpoiledMachineBlack = 0;
5167 }
5168
5169 void
5170 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5171 {
5172     char user_move[MSG_SIZ];
5173     char suffix[4];
5174
5175     if(gameInfo.variant == VariantSChess && promoChar) {
5176         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5177         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5178     } else suffix[0] = NULLCHAR;
5179
5180     switch (moveType) {
5181       default:
5182         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5183                 (int)moveType, fromX, fromY, toX, toY);
5184         DisplayError(user_move + strlen("say "), 0);
5185         break;
5186       case WhiteKingSideCastle:
5187       case BlackKingSideCastle:
5188       case WhiteQueenSideCastleWild:
5189       case BlackQueenSideCastleWild:
5190       /* PUSH Fabien */
5191       case WhiteHSideCastleFR:
5192       case BlackHSideCastleFR:
5193       /* POP Fabien */
5194         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5195         break;
5196       case WhiteQueenSideCastle:
5197       case BlackQueenSideCastle:
5198       case WhiteKingSideCastleWild:
5199       case BlackKingSideCastleWild:
5200       /* PUSH Fabien */
5201       case WhiteASideCastleFR:
5202       case BlackASideCastleFR:
5203       /* POP Fabien */
5204         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5205         break;
5206       case WhiteNonPromotion:
5207       case BlackNonPromotion:
5208         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5209         break;
5210       case WhitePromotion:
5211       case BlackPromotion:
5212         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5213            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5214           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5215                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5216                 PieceToChar(WhiteFerz));
5217         else if(gameInfo.variant == VariantGreat)
5218           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5220                 PieceToChar(WhiteMan));
5221         else
5222           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5223                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5224                 promoChar);
5225         break;
5226       case WhiteDrop:
5227       case BlackDrop:
5228       drop:
5229         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5230                  ToUpper(PieceToChar((ChessSquare) fromX)),
5231                  AAA + toX, ONE + toY);
5232         break;
5233       case IllegalMove:  /* could be a variant we don't quite understand */
5234         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5235       case NormalMove:
5236       case WhiteCapturesEnPassant:
5237       case BlackCapturesEnPassant:
5238         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5239                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5240         break;
5241     }
5242     SendToICS(user_move);
5243     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5244         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5245 }
5246
5247 void
5248 UploadGameEvent ()
5249 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5250     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5251     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5252     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5253       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5254       return;
5255     }
5256     if(gameMode != IcsExamining) { // is this ever not the case?
5257         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5258
5259         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5260           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5261         } else { // on FICS we must first go to general examine mode
5262           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5263         }
5264         if(gameInfo.variant != VariantNormal) {
5265             // try figure out wild number, as xboard names are not always valid on ICS
5266             for(i=1; i<=36; i++) {
5267               snprintf(buf, MSG_SIZ, "wild/%d", i);
5268                 if(StringToVariant(buf) == gameInfo.variant) break;
5269             }
5270             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5271             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5272             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5273         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5274         SendToICS(ics_prefix);
5275         SendToICS(buf);
5276         if(startedFromSetupPosition || backwardMostMove != 0) {
5277           fen = PositionToFEN(backwardMostMove, NULL, 1);
5278           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5279             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5280             SendToICS(buf);
5281           } else { // FICS: everything has to set by separate bsetup commands
5282             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5283             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5284             SendToICS(buf);
5285             if(!WhiteOnMove(backwardMostMove)) {
5286                 SendToICS("bsetup tomove black\n");
5287             }
5288             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5289             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5290             SendToICS(buf);
5291             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5292             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5293             SendToICS(buf);
5294             i = boards[backwardMostMove][EP_STATUS];
5295             if(i >= 0) { // set e.p.
5296               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5297                 SendToICS(buf);
5298             }
5299             bsetup++;
5300           }
5301         }
5302       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5303             SendToICS("bsetup done\n"); // switch to normal examining.
5304     }
5305     for(i = backwardMostMove; i<last; i++) {
5306         char buf[20];
5307         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5308         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5309             int len = strlen(moveList[i]);
5310             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5311             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5312         }
5313         SendToICS(buf);
5314     }
5315     SendToICS(ics_prefix);
5316     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5317 }
5318
5319 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5320
5321 void
5322 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5323 {
5324     if (rf == DROP_RANK) {
5325       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5326       sprintf(move, "%c@%c%c\n",
5327                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5328     } else {
5329         if (promoChar == 'x' || promoChar == NULLCHAR) {
5330           sprintf(move, "%c%c%c%c\n",
5331                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5332           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5333         } else {
5334             sprintf(move, "%c%c%c%c%c\n",
5335                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5336         }
5337     }
5338 }
5339
5340 void
5341 ProcessICSInitScript (FILE *f)
5342 {
5343     char buf[MSG_SIZ];
5344
5345     while (fgets(buf, MSG_SIZ, f)) {
5346         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5347     }
5348
5349     fclose(f);
5350 }
5351
5352
5353 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5354 int dragging;
5355 static ClickType lastClickType;
5356
5357 int
5358 Partner (ChessSquare *p)
5359 { // change piece into promotion partner if one shogi-promotes to the other
5360   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5361   ChessSquare partner;
5362   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5363   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5364   *p = partner;
5365   return 1;
5366 }
5367
5368 void
5369 Sweep (int step)
5370 {
5371     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5372     static int toggleFlag;
5373     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5374     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5375     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5376     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5377     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5378     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5379     do {
5380         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5381         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5382         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5383         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5384         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5385         if(!step) step = -1;
5386     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5387             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5388             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5389             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5390     if(toX >= 0) {
5391         int victim = boards[currentMove][toY][toX];
5392         boards[currentMove][toY][toX] = promoSweep;
5393         DrawPosition(FALSE, boards[currentMove]);
5394         boards[currentMove][toY][toX] = victim;
5395     } else
5396     ChangeDragPiece(promoSweep);
5397 }
5398
5399 int
5400 PromoScroll (int x, int y)
5401 {
5402   int step = 0;
5403
5404   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5405   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5406   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5407   if(!step) return FALSE;
5408   lastX = x; lastY = y;
5409   if((promoSweep < BlackPawn) == flipView) step = -step;
5410   if(step > 0) selectFlag = 1;
5411   if(!selectFlag) Sweep(step);
5412   return FALSE;
5413 }
5414
5415 void
5416 NextPiece (int step)
5417 {
5418     ChessSquare piece = boards[currentMove][toY][toX];
5419     do {
5420         pieceSweep -= step;
5421         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5422         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5423         if(!step) step = -1;
5424     } while(PieceToChar(pieceSweep) == '.');
5425     boards[currentMove][toY][toX] = pieceSweep;
5426     DrawPosition(FALSE, boards[currentMove]);
5427     boards[currentMove][toY][toX] = piece;
5428 }
5429 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5430 void
5431 AlphaRank (char *move, int n)
5432 {
5433 //    char *p = move, c; int x, y;
5434
5435     if (appData.debugMode) {
5436         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5437     }
5438
5439     if(move[1]=='*' &&
5440        move[2]>='0' && move[2]<='9' &&
5441        move[3]>='a' && move[3]<='x'    ) {
5442         move[1] = '@';
5443         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5444         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5445     } else
5446     if(move[0]>='0' && move[0]<='9' &&
5447        move[1]>='a' && move[1]<='x' &&
5448        move[2]>='0' && move[2]<='9' &&
5449        move[3]>='a' && move[3]<='x'    ) {
5450         /* input move, Shogi -> normal */
5451         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5452         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5453         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5454         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5455     } else
5456     if(move[1]=='@' &&
5457        move[3]>='0' && move[3]<='9' &&
5458        move[2]>='a' && move[2]<='x'    ) {
5459         move[1] = '*';
5460         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5461         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5462     } else
5463     if(
5464        move[0]>='a' && move[0]<='x' &&
5465        move[3]>='0' && move[3]<='9' &&
5466        move[2]>='a' && move[2]<='x'    ) {
5467          /* output move, normal -> Shogi */
5468         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5469         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5470         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5471         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5472         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5473     }
5474     if (appData.debugMode) {
5475         fprintf(debugFP, "   out = '%s'\n", move);
5476     }
5477 }
5478
5479 char yy_textstr[8000];
5480
5481 /* Parser for moves from gnuchess, ICS, or user typein box */
5482 Boolean
5483 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5484 {
5485     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5486
5487     switch (*moveType) {
5488       case WhitePromotion:
5489       case BlackPromotion:
5490       case WhiteNonPromotion:
5491       case BlackNonPromotion:
5492       case NormalMove:
5493       case FirstLeg:
5494       case WhiteCapturesEnPassant:
5495       case BlackCapturesEnPassant:
5496       case WhiteKingSideCastle:
5497       case WhiteQueenSideCastle:
5498       case BlackKingSideCastle:
5499       case BlackQueenSideCastle:
5500       case WhiteKingSideCastleWild:
5501       case WhiteQueenSideCastleWild:
5502       case BlackKingSideCastleWild:
5503       case BlackQueenSideCastleWild:
5504       /* Code added by Tord: */
5505       case WhiteHSideCastleFR:
5506       case WhiteASideCastleFR:
5507       case BlackHSideCastleFR:
5508       case BlackASideCastleFR:
5509       /* End of code added by Tord */
5510       case IllegalMove:         /* bug or odd chess variant */
5511         *fromX = currentMoveString[0] - AAA;
5512         *fromY = currentMoveString[1] - ONE;
5513         *toX = currentMoveString[2] - AAA;
5514         *toY = currentMoveString[3] - ONE;
5515         *promoChar = currentMoveString[4];
5516         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5517             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5518     if (appData.debugMode) {
5519         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5520     }
5521             *fromX = *fromY = *toX = *toY = 0;
5522             return FALSE;
5523         }
5524         if (appData.testLegality) {
5525           return (*moveType != IllegalMove);
5526         } else {
5527           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5528                          // [HGM] lion: if this is a double move we are less critical
5529                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5530         }
5531
5532       case WhiteDrop:
5533       case BlackDrop:
5534         *fromX = *moveType == WhiteDrop ?
5535           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5536           (int) CharToPiece(ToLower(currentMoveString[0]));
5537         *fromY = DROP_RANK;
5538         *toX = currentMoveString[2] - AAA;
5539         *toY = currentMoveString[3] - ONE;
5540         *promoChar = NULLCHAR;
5541         return TRUE;
5542
5543       case AmbiguousMove:
5544       case ImpossibleMove:
5545       case EndOfFile:
5546       case ElapsedTime:
5547       case Comment:
5548       case PGNTag:
5549       case NAG:
5550       case WhiteWins:
5551       case BlackWins:
5552       case GameIsDrawn:
5553       default:
5554     if (appData.debugMode) {
5555         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5556     }
5557         /* bug? */
5558         *fromX = *fromY = *toX = *toY = 0;
5559         *promoChar = NULLCHAR;
5560         return FALSE;
5561     }
5562 }
5563
5564 Boolean pushed = FALSE;
5565 char *lastParseAttempt;
5566
5567 void
5568 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5569 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5570   int fromX, fromY, toX, toY; char promoChar;
5571   ChessMove moveType;
5572   Boolean valid;
5573   int nr = 0;
5574
5575   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5576   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5577     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5578     pushed = TRUE;
5579   }
5580   endPV = forwardMostMove;
5581   do {
5582     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5583     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5584     lastParseAttempt = pv;
5585     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5586     if(!valid && nr == 0 &&
5587        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5588         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5589         // Hande case where played move is different from leading PV move
5590         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5591         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5592         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5593         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5594           endPV += 2; // if position different, keep this
5595           moveList[endPV-1][0] = fromX + AAA;
5596           moveList[endPV-1][1] = fromY + ONE;
5597           moveList[endPV-1][2] = toX + AAA;
5598           moveList[endPV-1][3] = toY + ONE;
5599           parseList[endPV-1][0] = NULLCHAR;
5600           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5601         }
5602       }
5603     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5604     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5605     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5606     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5607         valid++; // allow comments in PV
5608         continue;
5609     }
5610     nr++;
5611     if(endPV+1 > framePtr) break; // no space, truncate
5612     if(!valid) break;
5613     endPV++;
5614     CopyBoard(boards[endPV], boards[endPV-1]);
5615     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5616     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5617     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5618     CoordsToAlgebraic(boards[endPV - 1],
5619                              PosFlags(endPV - 1),
5620                              fromY, fromX, toY, toX, promoChar,
5621                              parseList[endPV - 1]);
5622   } while(valid);
5623   if(atEnd == 2) return; // used hidden, for PV conversion
5624   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5625   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5626   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5627                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5628   DrawPosition(TRUE, boards[currentMove]);
5629 }
5630
5631 int
5632 MultiPV (ChessProgramState *cps)
5633 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5634         int i;
5635         for(i=0; i<cps->nrOptions; i++)
5636             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5637                 return i;
5638         return -1;
5639 }
5640
5641 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5642
5643 Boolean
5644 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5645 {
5646         int startPV, multi, lineStart, origIndex = index;
5647         char *p, buf2[MSG_SIZ];
5648         ChessProgramState *cps = (pane ? &second : &first);
5649
5650         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5651         lastX = x; lastY = y;
5652         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5653         lineStart = startPV = index;
5654         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5655         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5656         index = startPV;
5657         do{ while(buf[index] && buf[index] != '\n') index++;
5658         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5659         buf[index] = 0;
5660         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5661                 int n = cps->option[multi].value;
5662                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5663                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5664                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5665                 cps->option[multi].value = n;
5666                 *start = *end = 0;
5667                 return FALSE;
5668         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5669                 ExcludeClick(origIndex - lineStart);
5670                 return FALSE;
5671         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5672                 Collapse(origIndex - lineStart);
5673                 return FALSE;
5674         }
5675         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5676         *start = startPV; *end = index-1;
5677         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5678         return TRUE;
5679 }
5680
5681 char *
5682 PvToSAN (char *pv)
5683 {
5684         static char buf[10*MSG_SIZ];
5685         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5686         *buf = NULLCHAR;
5687         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5688         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5689         for(i = forwardMostMove; i<endPV; i++){
5690             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5691             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5692             k += strlen(buf+k);
5693         }
5694         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5695         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5696         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5697         endPV = savedEnd;
5698         return buf;
5699 }
5700
5701 Boolean
5702 LoadPV (int x, int y)
5703 { // called on right mouse click to load PV
5704   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5705   lastX = x; lastY = y;
5706   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5707   extendGame = FALSE;
5708   return TRUE;
5709 }
5710
5711 void
5712 UnLoadPV ()
5713 {
5714   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5715   if(endPV < 0) return;
5716   if(appData.autoCopyPV) CopyFENToClipboard();
5717   endPV = -1;
5718   if(extendGame && currentMove > forwardMostMove) {
5719         Boolean saveAnimate = appData.animate;
5720         if(pushed) {
5721             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5722                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5723             } else storedGames--; // abandon shelved tail of original game
5724         }
5725         pushed = FALSE;
5726         forwardMostMove = currentMove;
5727         currentMove = oldFMM;
5728         appData.animate = FALSE;
5729         ToNrEvent(forwardMostMove);
5730         appData.animate = saveAnimate;
5731   }
5732   currentMove = forwardMostMove;
5733   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5734   ClearPremoveHighlights();
5735   DrawPosition(TRUE, boards[currentMove]);
5736 }
5737
5738 void
5739 MovePV (int x, int y, int h)
5740 { // step through PV based on mouse coordinates (called on mouse move)
5741   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5742
5743   // we must somehow check if right button is still down (might be released off board!)
5744   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5745   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5746   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5747   if(!step) return;
5748   lastX = x; lastY = y;
5749
5750   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5751   if(endPV < 0) return;
5752   if(y < margin) step = 1; else
5753   if(y > h - margin) step = -1;
5754   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5755   currentMove += step;
5756   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5757   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5758                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5759   DrawPosition(FALSE, boards[currentMove]);
5760 }
5761
5762
5763 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5764 // All positions will have equal probability, but the current method will not provide a unique
5765 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5766 #define DARK 1
5767 #define LITE 2
5768 #define ANY 3
5769
5770 int squaresLeft[4];
5771 int piecesLeft[(int)BlackPawn];
5772 int seed, nrOfShuffles;
5773
5774 void
5775 GetPositionNumber ()
5776 {       // sets global variable seed
5777         int i;
5778
5779         seed = appData.defaultFrcPosition;
5780         if(seed < 0) { // randomize based on time for negative FRC position numbers
5781                 for(i=0; i<50; i++) seed += random();
5782                 seed = random() ^ random() >> 8 ^ random() << 8;
5783                 if(seed<0) seed = -seed;
5784         }
5785 }
5786
5787 int
5788 put (Board board, int pieceType, int rank, int n, int shade)
5789 // put the piece on the (n-1)-th empty squares of the given shade
5790 {
5791         int i;
5792
5793         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5794                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5795                         board[rank][i] = (ChessSquare) pieceType;
5796                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5797                         squaresLeft[ANY]--;
5798                         piecesLeft[pieceType]--;
5799                         return i;
5800                 }
5801         }
5802         return -1;
5803 }
5804
5805
5806 void
5807 AddOnePiece (Board board, int pieceType, int rank, int shade)
5808 // calculate where the next piece goes, (any empty square), and put it there
5809 {
5810         int i;
5811
5812         i = seed % squaresLeft[shade];
5813         nrOfShuffles *= squaresLeft[shade];
5814         seed /= squaresLeft[shade];
5815         put(board, pieceType, rank, i, shade);
5816 }
5817
5818 void
5819 AddTwoPieces (Board board, int pieceType, int rank)
5820 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5821 {
5822         int i, n=squaresLeft[ANY], j=n-1, k;
5823
5824         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5825         i = seed % k;  // pick one
5826         nrOfShuffles *= k;
5827         seed /= k;
5828         while(i >= j) i -= j--;
5829         j = n - 1 - j; i += j;
5830         put(board, pieceType, rank, j, ANY);
5831         put(board, pieceType, rank, i, ANY);
5832 }
5833
5834 void
5835 SetUpShuffle (Board board, int number)
5836 {
5837         int i, p, first=1;
5838
5839         GetPositionNumber(); nrOfShuffles = 1;
5840
5841         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5842         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5843         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5844
5845         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5846
5847         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5848             p = (int) board[0][i];
5849             if(p < (int) BlackPawn) piecesLeft[p] ++;
5850             board[0][i] = EmptySquare;
5851         }
5852
5853         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5854             // shuffles restricted to allow normal castling put KRR first
5855             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5856                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5857             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5858                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5859             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5860                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5861             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5862                 put(board, WhiteRook, 0, 0, ANY);
5863             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5864         }
5865
5866         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5867             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5868             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5869                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5870                 while(piecesLeft[p] >= 2) {
5871                     AddOnePiece(board, p, 0, LITE);
5872                     AddOnePiece(board, p, 0, DARK);
5873                 }
5874                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5875             }
5876
5877         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5878             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5879             // but we leave King and Rooks for last, to possibly obey FRC restriction
5880             if(p == (int)WhiteRook) continue;
5881             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5882             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5883         }
5884
5885         // now everything is placed, except perhaps King (Unicorn) and Rooks
5886
5887         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5888             // Last King gets castling rights
5889             while(piecesLeft[(int)WhiteUnicorn]) {
5890                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5891                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5892             }
5893
5894             while(piecesLeft[(int)WhiteKing]) {
5895                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5896                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5897             }
5898
5899
5900         } else {
5901             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5902             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5903         }
5904
5905         // Only Rooks can be left; simply place them all
5906         while(piecesLeft[(int)WhiteRook]) {
5907                 i = put(board, WhiteRook, 0, 0, ANY);
5908                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5909                         if(first) {
5910                                 first=0;
5911                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5912                         }
5913                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5914                 }
5915         }
5916         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5917             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5918         }
5919
5920         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5921 }
5922
5923 int
5924 SetCharTable (char *table, const char * map)
5925 /* [HGM] moved here from winboard.c because of its general usefulness */
5926 /*       Basically a safe strcpy that uses the last character as King */
5927 {
5928     int result = FALSE; int NrPieces;
5929
5930     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5931                     && NrPieces >= 12 && !(NrPieces&1)) {
5932         int i; /* [HGM] Accept even length from 12 to 34 */
5933
5934         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5935         for( i=0; i<NrPieces/2-1; i++ ) {
5936             table[i] = map[i];
5937             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5938         }
5939         table[(int) WhiteKing]  = map[NrPieces/2-1];
5940         table[(int) BlackKing]  = map[NrPieces-1];
5941
5942         result = TRUE;
5943     }
5944
5945     return result;
5946 }
5947
5948 void
5949 Prelude (Board board)
5950 {       // [HGM] superchess: random selection of exo-pieces
5951         int i, j, k; ChessSquare p;
5952         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5953
5954         GetPositionNumber(); // use FRC position number
5955
5956         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5957             SetCharTable(pieceToChar, appData.pieceToCharTable);
5958             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5959                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5960         }
5961
5962         j = seed%4;                 seed /= 4;
5963         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5964         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5965         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5966         j = seed%3 + (seed%3 >= j); seed /= 3;
5967         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5968         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5969         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5970         j = seed%3;                 seed /= 3;
5971         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5972         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5973         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5974         j = seed%2 + (seed%2 >= j); seed /= 2;
5975         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5978         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5979         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5980         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5981         put(board, exoPieces[0],    0, 0, ANY);
5982         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5983 }
5984
5985 void
5986 InitPosition (int redraw)
5987 {
5988     ChessSquare (* pieces)[BOARD_FILES];
5989     int i, j, pawnRow=1, pieceRows=1, overrule,
5990     oldx = gameInfo.boardWidth,
5991     oldy = gameInfo.boardHeight,
5992     oldh = gameInfo.holdingsWidth;
5993     static int oldv;
5994
5995     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5996
5997     /* [AS] Initialize pv info list [HGM] and game status */
5998     {
5999         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6000             pvInfoList[i].depth = 0;
6001             boards[i][EP_STATUS] = EP_NONE;
6002             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6003         }
6004
6005         initialRulePlies = 0; /* 50-move counter start */
6006
6007         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6008         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6009     }
6010
6011
6012     /* [HGM] logic here is completely changed. In stead of full positions */
6013     /* the initialized data only consist of the two backranks. The switch */
6014     /* selects which one we will use, which is than copied to the Board   */
6015     /* initialPosition, which for the rest is initialized by Pawns and    */
6016     /* empty squares. This initial position is then copied to boards[0],  */
6017     /* possibly after shuffling, so that it remains available.            */
6018
6019     gameInfo.holdingsWidth = 0; /* default board sizes */
6020     gameInfo.boardWidth    = 8;
6021     gameInfo.boardHeight   = 8;
6022     gameInfo.holdingsSize  = 0;
6023     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6024     for(i=0; i<BOARD_FILES-2; i++)
6025       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6026     initialPosition[EP_STATUS] = EP_NONE;
6027     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6028     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6029          SetCharTable(pieceNickName, appData.pieceNickNames);
6030     else SetCharTable(pieceNickName, "............");
6031     pieces = FIDEArray;
6032
6033     switch (gameInfo.variant) {
6034     case VariantFischeRandom:
6035       shuffleOpenings = TRUE;
6036       appData.fischerCastling = TRUE;
6037     default:
6038       break;
6039     case VariantShatranj:
6040       pieces = ShatranjArray;
6041       nrCastlingRights = 0;
6042       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6043       break;
6044     case VariantMakruk:
6045       pieces = makrukArray;
6046       nrCastlingRights = 0;
6047       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6048       break;
6049     case VariantASEAN:
6050       pieces = aseanArray;
6051       nrCastlingRights = 0;
6052       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6053       break;
6054     case VariantTwoKings:
6055       pieces = twoKingsArray;
6056       break;
6057     case VariantGrand:
6058       pieces = GrandArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6061       gameInfo.boardWidth = 10;
6062       gameInfo.boardHeight = 10;
6063       gameInfo.holdingsSize = 7;
6064       break;
6065     case VariantCapaRandom:
6066       shuffleOpenings = TRUE;
6067       appData.fischerCastling = TRUE;
6068     case VariantCapablanca:
6069       pieces = CapablancaArray;
6070       gameInfo.boardWidth = 10;
6071       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6072       break;
6073     case VariantGothic:
6074       pieces = GothicArray;
6075       gameInfo.boardWidth = 10;
6076       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6077       break;
6078     case VariantSChess:
6079       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6080       gameInfo.holdingsSize = 7;
6081       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6082       break;
6083     case VariantJanus:
6084       pieces = JanusArray;
6085       gameInfo.boardWidth = 10;
6086       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6087       nrCastlingRights = 6;
6088         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6089         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6090         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6091         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6092         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6093         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6094       break;
6095     case VariantFalcon:
6096       pieces = FalconArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6099       break;
6100     case VariantXiangqi:
6101       pieces = XiangqiArray;
6102       gameInfo.boardWidth  = 9;
6103       gameInfo.boardHeight = 10;
6104       nrCastlingRights = 0;
6105       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6106       break;
6107     case VariantShogi:
6108       pieces = ShogiArray;
6109       gameInfo.boardWidth  = 9;
6110       gameInfo.boardHeight = 9;
6111       gameInfo.holdingsSize = 7;
6112       nrCastlingRights = 0;
6113       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6114       break;
6115     case VariantChu:
6116       pieces = ChuArray; pieceRows = 3;
6117       gameInfo.boardWidth  = 12;
6118       gameInfo.boardHeight = 12;
6119       nrCastlingRights = 0;
6120       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6121                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6122       break;
6123     case VariantCourier:
6124       pieces = CourierArray;
6125       gameInfo.boardWidth  = 12;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6128       break;
6129     case VariantKnightmate:
6130       pieces = KnightmateArray;
6131       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6132       break;
6133     case VariantSpartan:
6134       pieces = SpartanArray;
6135       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6136       break;
6137     case VariantLion:
6138       pieces = lionArray;
6139       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6140       break;
6141     case VariantChuChess:
6142       pieces = ChuChessArray;
6143       gameInfo.boardWidth = 10;
6144       gameInfo.boardHeight = 10;
6145       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6146       break;
6147     case VariantFairy:
6148       pieces = fairyArray;
6149       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6150       break;
6151     case VariantGreat:
6152       pieces = GreatArray;
6153       gameInfo.boardWidth = 10;
6154       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6155       gameInfo.holdingsSize = 8;
6156       break;
6157     case VariantSuper:
6158       pieces = FIDEArray;
6159       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6160       gameInfo.holdingsSize = 8;
6161       startedFromSetupPosition = TRUE;
6162       break;
6163     case VariantCrazyhouse:
6164     case VariantBughouse:
6165       pieces = FIDEArray;
6166       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6167       gameInfo.holdingsSize = 5;
6168       break;
6169     case VariantWildCastle:
6170       pieces = FIDEArray;
6171       /* !!?shuffle with kings guaranteed to be on d or e file */
6172       shuffleOpenings = 1;
6173       break;
6174     case VariantNoCastle:
6175       pieces = FIDEArray;
6176       nrCastlingRights = 0;
6177       /* !!?unconstrained back-rank shuffle */
6178       shuffleOpenings = 1;
6179       break;
6180     }
6181
6182     overrule = 0;
6183     if(appData.NrFiles >= 0) {
6184         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6185         gameInfo.boardWidth = appData.NrFiles;
6186     }
6187     if(appData.NrRanks >= 0) {
6188         gameInfo.boardHeight = appData.NrRanks;
6189     }
6190     if(appData.holdingsSize >= 0) {
6191         i = appData.holdingsSize;
6192         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6193         gameInfo.holdingsSize = i;
6194     }
6195     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6196     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6197         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6198
6199     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6200     if(pawnRow < 1) pawnRow = 1;
6201     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6202        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6203     if(gameInfo.variant == VariantChu) pawnRow = 3;
6204
6205     /* User pieceToChar list overrules defaults */
6206     if(appData.pieceToCharTable != NULL)
6207         SetCharTable(pieceToChar, appData.pieceToCharTable);
6208
6209     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6210
6211         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6212             s = (ChessSquare) 0; /* account holding counts in guard band */
6213         for( i=0; i<BOARD_HEIGHT; i++ )
6214             initialPosition[i][j] = s;
6215
6216         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6217         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6218         initialPosition[pawnRow][j] = WhitePawn;
6219         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6220         if(gameInfo.variant == VariantXiangqi) {
6221             if(j&1) {
6222                 initialPosition[pawnRow][j] =
6223                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6224                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6225                    initialPosition[2][j] = WhiteCannon;
6226                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6227                 }
6228             }
6229         }
6230         if(gameInfo.variant == VariantChu) {
6231              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6232                initialPosition[pawnRow+1][j] = WhiteCobra,
6233                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6234              for(i=1; i<pieceRows; i++) {
6235                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6236                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6237              }
6238         }
6239         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6240             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6241                initialPosition[0][j] = WhiteRook;
6242                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6243             }
6244         }
6245         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6246     }
6247     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6248     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6249
6250             j=BOARD_LEFT+1;
6251             initialPosition[1][j] = WhiteBishop;
6252             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6253             j=BOARD_RGHT-2;
6254             initialPosition[1][j] = WhiteRook;
6255             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6256     }
6257
6258     if( nrCastlingRights == -1) {
6259         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6260         /*       This sets default castling rights from none to normal corners   */
6261         /* Variants with other castling rights must set them themselves above    */
6262         nrCastlingRights = 6;
6263
6264         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6265         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6266         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6267         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6268         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6269         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6270      }
6271
6272      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6273      if(gameInfo.variant == VariantGreat) { // promotion commoners
6274         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6275         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6276         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6277         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6278      }
6279      if( gameInfo.variant == VariantSChess ) {
6280       initialPosition[1][0] = BlackMarshall;
6281       initialPosition[2][0] = BlackAngel;
6282       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6283       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6284       initialPosition[1][1] = initialPosition[2][1] =
6285       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6286      }
6287   if (appData.debugMode) {
6288     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6289   }
6290     if(shuffleOpenings) {
6291         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6292         startedFromSetupPosition = TRUE;
6293     }
6294     if(startedFromPositionFile) {
6295       /* [HGM] loadPos: use PositionFile for every new game */
6296       CopyBoard(initialPosition, filePosition);
6297       for(i=0; i<nrCastlingRights; i++)
6298           initialRights[i] = filePosition[CASTLING][i];
6299       startedFromSetupPosition = TRUE;
6300     }
6301
6302     CopyBoard(boards[0], initialPosition);
6303
6304     if(oldx != gameInfo.boardWidth ||
6305        oldy != gameInfo.boardHeight ||
6306        oldv != gameInfo.variant ||
6307        oldh != gameInfo.holdingsWidth
6308                                          )
6309             InitDrawingSizes(-2 ,0);
6310
6311     oldv = gameInfo.variant;
6312     if (redraw)
6313       DrawPosition(TRUE, boards[currentMove]);
6314 }
6315
6316 void
6317 SendBoard (ChessProgramState *cps, int moveNum)
6318 {
6319     char message[MSG_SIZ];
6320
6321     if (cps->useSetboard) {
6322       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6323       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6324       SendToProgram(message, cps);
6325       free(fen);
6326
6327     } else {
6328       ChessSquare *bp;
6329       int i, j, left=0, right=BOARD_WIDTH;
6330       /* Kludge to set black to move, avoiding the troublesome and now
6331        * deprecated "black" command.
6332        */
6333       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6334         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6335
6336       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6337
6338       SendToProgram("edit\n", cps);
6339       SendToProgram("#\n", cps);
6340       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6341         bp = &boards[moveNum][i][left];
6342         for (j = left; j < right; j++, bp++) {
6343           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6344           if ((int) *bp < (int) BlackPawn) {
6345             if(j == BOARD_RGHT+1)
6346                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6347             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6348             if(message[0] == '+' || message[0] == '~') {
6349               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6350                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6351                         AAA + j, ONE + i);
6352             }
6353             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6354                 message[1] = BOARD_RGHT   - 1 - j + '1';
6355                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6356             }
6357             SendToProgram(message, cps);
6358           }
6359         }
6360       }
6361
6362       SendToProgram("c\n", cps);
6363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6364         bp = &boards[moveNum][i][left];
6365         for (j = left; j < right; j++, bp++) {
6366           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6367           if (((int) *bp != (int) EmptySquare)
6368               && ((int) *bp >= (int) BlackPawn)) {
6369             if(j == BOARD_LEFT-2)
6370                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6371             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6372                     AAA + j, ONE + i);
6373             if(message[0] == '+' || message[0] == '~') {
6374               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6375                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6376                         AAA + j, ONE + i);
6377             }
6378             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6379                 message[1] = BOARD_RGHT   - 1 - j + '1';
6380                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6381             }
6382             SendToProgram(message, cps);
6383           }
6384         }
6385       }
6386
6387       SendToProgram(".\n", cps);
6388     }
6389     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6390 }
6391
6392 char exclusionHeader[MSG_SIZ];
6393 int exCnt, excludePtr;
6394 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6395 static Exclusion excluTab[200];
6396 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6397
6398 static void
6399 WriteMap (int s)
6400 {
6401     int j;
6402     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6403     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6404 }
6405
6406 static void
6407 ClearMap ()
6408 {
6409     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6410     excludePtr = 24; exCnt = 0;
6411     WriteMap(0);
6412 }
6413
6414 static void
6415 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6416 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6417     char buf[2*MOVE_LEN], *p;
6418     Exclusion *e = excluTab;
6419     int i;
6420     for(i=0; i<exCnt; i++)
6421         if(e[i].ff == fromX && e[i].fr == fromY &&
6422            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6423     if(i == exCnt) { // was not in exclude list; add it
6424         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6425         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6426             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6427             return; // abort
6428         }
6429         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6430         excludePtr++; e[i].mark = excludePtr++;
6431         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6432         exCnt++;
6433     }
6434     exclusionHeader[e[i].mark] = state;
6435 }
6436
6437 static int
6438 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6439 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6440     char buf[MSG_SIZ];
6441     int j, k;
6442     ChessMove moveType;
6443     if((signed char)promoChar == -1) { // kludge to indicate best move
6444         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6445             return 1; // if unparsable, abort
6446     }
6447     // update exclusion map (resolving toggle by consulting existing state)
6448     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6449     j = k%8; k >>= 3;
6450     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6451     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6452          excludeMap[k] |=   1<<j;
6453     else excludeMap[k] &= ~(1<<j);
6454     // update header
6455     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6456     // inform engine
6457     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6458     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6459     SendToBoth(buf);
6460     return (state == '+');
6461 }
6462
6463 static void
6464 ExcludeClick (int index)
6465 {
6466     int i, j;
6467     Exclusion *e = excluTab;
6468     if(index < 25) { // none, best or tail clicked
6469         if(index < 13) { // none: include all
6470             WriteMap(0); // clear map
6471             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6472             SendToBoth("include all\n"); // and inform engine
6473         } else if(index > 18) { // tail
6474             if(exclusionHeader[19] == '-') { // tail was excluded
6475                 SendToBoth("include all\n");
6476                 WriteMap(0); // clear map completely
6477                 // now re-exclude selected moves
6478                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6479                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6480             } else { // tail was included or in mixed state
6481                 SendToBoth("exclude all\n");
6482                 WriteMap(0xFF); // fill map completely
6483                 // now re-include selected moves
6484                 j = 0; // count them
6485                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6486                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6487                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6488             }
6489         } else { // best
6490             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6491         }
6492     } else {
6493         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6494             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6495             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6496             break;
6497         }
6498     }
6499 }
6500
6501 ChessSquare
6502 DefaultPromoChoice (int white)
6503 {
6504     ChessSquare result;
6505     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6506        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6507         result = WhiteFerz; // no choice
6508     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6509         result= WhiteKing; // in Suicide Q is the last thing we want
6510     else if(gameInfo.variant == VariantSpartan)
6511         result = white ? WhiteQueen : WhiteAngel;
6512     else result = WhiteQueen;
6513     if(!white) result = WHITE_TO_BLACK result;
6514     return result;
6515 }
6516
6517 static int autoQueen; // [HGM] oneclick
6518
6519 int
6520 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6521 {
6522     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6523     /* [HGM] add Shogi promotions */
6524     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6525     ChessSquare piece, partner;
6526     ChessMove moveType;
6527     Boolean premove;
6528
6529     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6530     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6531
6532     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6533       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6534         return FALSE;
6535
6536     piece = boards[currentMove][fromY][fromX];
6537     if(gameInfo.variant == VariantChu) {
6538         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6539         promotionZoneSize = BOARD_HEIGHT/3;
6540         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6541     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6542         promotionZoneSize = BOARD_HEIGHT/3;
6543         highestPromotingPiece = (int)WhiteAlfil;
6544     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6545         promotionZoneSize = 3;
6546     }
6547
6548     // Treat Lance as Pawn when it is not representing Amazon or Lance
6549     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6550         if(piece == WhiteLance) piece = WhitePawn; else
6551         if(piece == BlackLance) piece = BlackPawn;
6552     }
6553
6554     // next weed out all moves that do not touch the promotion zone at all
6555     if((int)piece >= BlackPawn) {
6556         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6557              return FALSE;
6558         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6559         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6560     } else {
6561         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6562            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6563         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6564              return FALSE;
6565     }
6566
6567     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6568
6569     // weed out mandatory Shogi promotions
6570     if(gameInfo.variant == VariantShogi) {
6571         if(piece >= BlackPawn) {
6572             if(toY == 0 && piece == BlackPawn ||
6573                toY == 0 && piece == BlackQueen ||
6574                toY <= 1 && piece == BlackKnight) {
6575                 *promoChoice = '+';
6576                 return FALSE;
6577             }
6578         } else {
6579             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6580                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6581                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6582                 *promoChoice = '+';
6583                 return FALSE;
6584             }
6585         }
6586     }
6587
6588     // weed out obviously illegal Pawn moves
6589     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6590         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6591         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6592         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6593         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6594         // note we are not allowed to test for valid (non-)capture, due to premove
6595     }
6596
6597     // we either have a choice what to promote to, or (in Shogi) whether to promote
6598     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6599        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6600         ChessSquare p=BlackFerz;  // no choice
6601         while(p < EmptySquare) {  //but make sure we use piece that exists
6602             *promoChoice = PieceToChar(p++);
6603             if(*promoChoice != '.') break;
6604         }
6605         return FALSE;
6606     }
6607     // no sense asking what we must promote to if it is going to explode...
6608     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6609         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6610         return FALSE;
6611     }
6612     // give caller the default choice even if we will not make it
6613     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6614     partner = piece; // pieces can promote if the pieceToCharTable says so
6615     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6616     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6617     if(        sweepSelect && gameInfo.variant != VariantGreat
6618                            && gameInfo.variant != VariantGrand
6619                            && gameInfo.variant != VariantSuper) return FALSE;
6620     if(autoQueen) return FALSE; // predetermined
6621
6622     // suppress promotion popup on illegal moves that are not premoves
6623     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6624               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6625     if(appData.testLegality && !premove) {
6626         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6627                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6628         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6629         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6630             return FALSE;
6631     }
6632
6633     return TRUE;
6634 }
6635
6636 int
6637 InPalace (int row, int column)
6638 {   /* [HGM] for Xiangqi */
6639     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6640          column < (BOARD_WIDTH + 4)/2 &&
6641          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6642     return FALSE;
6643 }
6644
6645 int
6646 PieceForSquare (int x, int y)
6647 {
6648   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6649      return -1;
6650   else
6651      return boards[currentMove][y][x];
6652 }
6653
6654 int
6655 OKToStartUserMove (int x, int y)
6656 {
6657     ChessSquare from_piece;
6658     int white_piece;
6659
6660     if (matchMode) return FALSE;
6661     if (gameMode == EditPosition) return TRUE;
6662
6663     if (x >= 0 && y >= 0)
6664       from_piece = boards[currentMove][y][x];
6665     else
6666       from_piece = EmptySquare;
6667
6668     if (from_piece == EmptySquare) return FALSE;
6669
6670     white_piece = (int)from_piece >= (int)WhitePawn &&
6671       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6672
6673     switch (gameMode) {
6674       case AnalyzeFile:
6675       case TwoMachinesPlay:
6676       case EndOfGame:
6677         return FALSE;
6678
6679       case IcsObserving:
6680       case IcsIdle:
6681         return FALSE;
6682
6683       case MachinePlaysWhite:
6684       case IcsPlayingBlack:
6685         if (appData.zippyPlay) return FALSE;
6686         if (white_piece) {
6687             DisplayMoveError(_("You are playing Black"));
6688             return FALSE;
6689         }
6690         break;
6691
6692       case MachinePlaysBlack:
6693       case IcsPlayingWhite:
6694         if (appData.zippyPlay) return FALSE;
6695         if (!white_piece) {
6696             DisplayMoveError(_("You are playing White"));
6697             return FALSE;
6698         }
6699         break;
6700
6701       case PlayFromGameFile:
6702             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6703       case EditGame:
6704         if (!white_piece && WhiteOnMove(currentMove)) {
6705             DisplayMoveError(_("It is White's turn"));
6706             return FALSE;
6707         }
6708         if (white_piece && !WhiteOnMove(currentMove)) {
6709             DisplayMoveError(_("It is Black's turn"));
6710             return FALSE;
6711         }
6712         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6713             /* Editing correspondence game history */
6714             /* Could disallow this or prompt for confirmation */
6715             cmailOldMove = -1;
6716         }
6717         break;
6718
6719       case BeginningOfGame:
6720         if (appData.icsActive) return FALSE;
6721         if (!appData.noChessProgram) {
6722             if (!white_piece) {
6723                 DisplayMoveError(_("You are playing White"));
6724                 return FALSE;
6725             }
6726         }
6727         break;
6728
6729       case Training:
6730         if (!white_piece && WhiteOnMove(currentMove)) {
6731             DisplayMoveError(_("It is White's turn"));
6732             return FALSE;
6733         }
6734         if (white_piece && !WhiteOnMove(currentMove)) {
6735             DisplayMoveError(_("It is Black's turn"));
6736             return FALSE;
6737         }
6738         break;
6739
6740       default:
6741       case IcsExamining:
6742         break;
6743     }
6744     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6745         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6746         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6747         && gameMode != AnalyzeFile && gameMode != Training) {
6748         DisplayMoveError(_("Displayed position is not current"));
6749         return FALSE;
6750     }
6751     return TRUE;
6752 }
6753
6754 Boolean
6755 OnlyMove (int *x, int *y, Boolean captures)
6756 {
6757     DisambiguateClosure cl;
6758     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6759     switch(gameMode) {
6760       case MachinePlaysBlack:
6761       case IcsPlayingWhite:
6762       case BeginningOfGame:
6763         if(!WhiteOnMove(currentMove)) return FALSE;
6764         break;
6765       case MachinePlaysWhite:
6766       case IcsPlayingBlack:
6767         if(WhiteOnMove(currentMove)) return FALSE;
6768         break;
6769       case EditGame:
6770         break;
6771       default:
6772         return FALSE;
6773     }
6774     cl.pieceIn = EmptySquare;
6775     cl.rfIn = *y;
6776     cl.ffIn = *x;
6777     cl.rtIn = -1;
6778     cl.ftIn = -1;
6779     cl.promoCharIn = NULLCHAR;
6780     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6781     if( cl.kind == NormalMove ||
6782         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6783         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6784         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6785       fromX = cl.ff;
6786       fromY = cl.rf;
6787       *x = cl.ft;
6788       *y = cl.rt;
6789       return TRUE;
6790     }
6791     if(cl.kind != ImpossibleMove) return FALSE;
6792     cl.pieceIn = EmptySquare;
6793     cl.rfIn = -1;
6794     cl.ffIn = -1;
6795     cl.rtIn = *y;
6796     cl.ftIn = *x;
6797     cl.promoCharIn = NULLCHAR;
6798     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6799     if( cl.kind == NormalMove ||
6800         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6801         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6802         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6803       fromX = cl.ff;
6804       fromY = cl.rf;
6805       *x = cl.ft;
6806       *y = cl.rt;
6807       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6808       return TRUE;
6809     }
6810     return FALSE;
6811 }
6812
6813 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6814 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6815 int lastLoadGameUseList = FALSE;
6816 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6817 ChessMove lastLoadGameStart = EndOfFile;
6818 int doubleClick;
6819 Boolean addToBookFlag;
6820
6821 void
6822 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6823 {
6824     ChessMove moveType;
6825     ChessSquare pup;
6826     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6827
6828     /* Check if the user is playing in turn.  This is complicated because we
6829        let the user "pick up" a piece before it is his turn.  So the piece he
6830        tried to pick up may have been captured by the time he puts it down!
6831        Therefore we use the color the user is supposed to be playing in this
6832        test, not the color of the piece that is currently on the starting
6833        square---except in EditGame mode, where the user is playing both
6834        sides; fortunately there the capture race can't happen.  (It can
6835        now happen in IcsExamining mode, but that's just too bad.  The user
6836        will get a somewhat confusing message in that case.)
6837        */
6838
6839     switch (gameMode) {
6840       case AnalyzeFile:
6841       case TwoMachinesPlay:
6842       case EndOfGame:
6843       case IcsObserving:
6844       case IcsIdle:
6845         /* We switched into a game mode where moves are not accepted,
6846            perhaps while the mouse button was down. */
6847         return;
6848
6849       case MachinePlaysWhite:
6850         /* User is moving for Black */
6851         if (WhiteOnMove(currentMove)) {
6852             DisplayMoveError(_("It is White's turn"));
6853             return;
6854         }
6855         break;
6856
6857       case MachinePlaysBlack:
6858         /* User is moving for White */
6859         if (!WhiteOnMove(currentMove)) {
6860             DisplayMoveError(_("It is Black's turn"));
6861             return;
6862         }
6863         break;
6864
6865       case PlayFromGameFile:
6866             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6867       case EditGame:
6868       case IcsExamining:
6869       case BeginningOfGame:
6870       case AnalyzeMode:
6871       case Training:
6872         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6873         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6874             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6875             /* User is moving for Black */
6876             if (WhiteOnMove(currentMove)) {
6877                 DisplayMoveError(_("It is White's turn"));
6878                 return;
6879             }
6880         } else {
6881             /* User is moving for White */
6882             if (!WhiteOnMove(currentMove)) {
6883                 DisplayMoveError(_("It is Black's turn"));
6884                 return;
6885             }
6886         }
6887         break;
6888
6889       case IcsPlayingBlack:
6890         /* User is moving for Black */
6891         if (WhiteOnMove(currentMove)) {
6892             if (!appData.premove) {
6893                 DisplayMoveError(_("It is White's turn"));
6894             } else if (toX >= 0 && toY >= 0) {
6895                 premoveToX = toX;
6896                 premoveToY = toY;
6897                 premoveFromX = fromX;
6898                 premoveFromY = fromY;
6899                 premovePromoChar = promoChar;
6900                 gotPremove = 1;
6901                 if (appData.debugMode)
6902                     fprintf(debugFP, "Got premove: fromX %d,"
6903                             "fromY %d, toX %d, toY %d\n",
6904                             fromX, fromY, toX, toY);
6905             }
6906             return;
6907         }
6908         break;
6909
6910       case IcsPlayingWhite:
6911         /* User is moving for White */
6912         if (!WhiteOnMove(currentMove)) {
6913             if (!appData.premove) {
6914                 DisplayMoveError(_("It is Black's turn"));
6915             } else if (toX >= 0 && toY >= 0) {
6916                 premoveToX = toX;
6917                 premoveToY = toY;
6918                 premoveFromX = fromX;
6919                 premoveFromY = fromY;
6920                 premovePromoChar = promoChar;
6921                 gotPremove = 1;
6922                 if (appData.debugMode)
6923                     fprintf(debugFP, "Got premove: fromX %d,"
6924                             "fromY %d, toX %d, toY %d\n",
6925                             fromX, fromY, toX, toY);
6926             }
6927             return;
6928         }
6929         break;
6930
6931       default:
6932         break;
6933
6934       case EditPosition:
6935         /* EditPosition, empty square, or different color piece;
6936            click-click move is possible */
6937         if (toX == -2 || toY == -2) {
6938             boards[0][fromY][fromX] = EmptySquare;
6939             DrawPosition(FALSE, boards[currentMove]);
6940             return;
6941         } else if (toX >= 0 && toY >= 0) {
6942             boards[0][toY][toX] = boards[0][fromY][fromX];
6943             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6944                 if(boards[0][fromY][0] != EmptySquare) {
6945                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6946                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6947                 }
6948             } else
6949             if(fromX == BOARD_RGHT+1) {
6950                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6951                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6952                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6953                 }
6954             } else
6955             boards[0][fromY][fromX] = gatingPiece;
6956             DrawPosition(FALSE, boards[currentMove]);
6957             return;
6958         }
6959         return;
6960     }
6961
6962     if(toX < 0 || toY < 0) return;
6963     pup = boards[currentMove][toY][toX];
6964
6965     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6966     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6967          if( pup != EmptySquare ) return;
6968          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6969            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6970                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6971            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6972            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6973            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6974            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6975          fromY = DROP_RANK;
6976     }
6977
6978     /* [HGM] always test for legality, to get promotion info */
6979     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6980                                          fromY, fromX, toY, toX, promoChar);
6981
6982     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6983
6984     /* [HGM] but possibly ignore an IllegalMove result */
6985     if (appData.testLegality) {
6986         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6987             DisplayMoveError(_("Illegal move"));
6988             return;
6989         }
6990     }
6991
6992     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6993         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6994              ClearPremoveHighlights(); // was included
6995         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6996         return;
6997     }
6998
6999     if(addToBookFlag) { // adding moves to book
7000         char buf[MSG_SIZ], move[MSG_SIZ];
7001         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7002         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7003         AddBookMove(buf);
7004         addToBookFlag = FALSE;
7005         ClearHighlights();
7006         return;
7007     }
7008
7009     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7010 }
7011
7012 /* Common tail of UserMoveEvent and DropMenuEvent */
7013 int
7014 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7015 {
7016     char *bookHit = 0;
7017
7018     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7019         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7020         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7021         if(WhiteOnMove(currentMove)) {
7022             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7023         } else {
7024             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7025         }
7026     }
7027
7028     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7029        move type in caller when we know the move is a legal promotion */
7030     if(moveType == NormalMove && promoChar)
7031         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7032
7033     /* [HGM] <popupFix> The following if has been moved here from
7034        UserMoveEvent(). Because it seemed to belong here (why not allow
7035        piece drops in training games?), and because it can only be
7036        performed after it is known to what we promote. */
7037     if (gameMode == Training) {
7038       /* compare the move played on the board to the next move in the
7039        * game. If they match, display the move and the opponent's response.
7040        * If they don't match, display an error message.
7041        */
7042       int saveAnimate;
7043       Board testBoard;
7044       CopyBoard(testBoard, boards[currentMove]);
7045       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7046
7047       if (CompareBoards(testBoard, boards[currentMove+1])) {
7048         ForwardInner(currentMove+1);
7049
7050         /* Autoplay the opponent's response.
7051          * if appData.animate was TRUE when Training mode was entered,
7052          * the response will be animated.
7053          */
7054         saveAnimate = appData.animate;
7055         appData.animate = animateTraining;
7056         ForwardInner(currentMove+1);
7057         appData.animate = saveAnimate;
7058
7059         /* check for the end of the game */
7060         if (currentMove >= forwardMostMove) {
7061           gameMode = PlayFromGameFile;
7062           ModeHighlight();
7063           SetTrainingModeOff();
7064           DisplayInformation(_("End of game"));
7065         }
7066       } else {
7067         DisplayError(_("Incorrect move"), 0);
7068       }
7069       return 1;
7070     }
7071
7072   /* Ok, now we know that the move is good, so we can kill
7073      the previous line in Analysis Mode */
7074   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7075                                 && currentMove < forwardMostMove) {
7076     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7077     else forwardMostMove = currentMove;
7078   }
7079
7080   ClearMap();
7081
7082   /* If we need the chess program but it's dead, restart it */
7083   ResurrectChessProgram();
7084
7085   /* A user move restarts a paused game*/
7086   if (pausing)
7087     PauseEvent();
7088
7089   thinkOutput[0] = NULLCHAR;
7090
7091   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7092
7093   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7094     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7095     return 1;
7096   }
7097
7098   if (gameMode == BeginningOfGame) {
7099     if (appData.noChessProgram) {
7100       gameMode = EditGame;
7101       SetGameInfo();
7102     } else {
7103       char buf[MSG_SIZ];
7104       gameMode = MachinePlaysBlack;
7105       StartClocks();
7106       SetGameInfo();
7107       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7108       DisplayTitle(buf);
7109       if (first.sendName) {
7110         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7111         SendToProgram(buf, &first);
7112       }
7113       StartClocks();
7114     }
7115     ModeHighlight();
7116   }
7117
7118   /* Relay move to ICS or chess engine */
7119   if (appData.icsActive) {
7120     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7121         gameMode == IcsExamining) {
7122       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7123         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7124         SendToICS("draw ");
7125         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7126       }
7127       // also send plain move, in case ICS does not understand atomic claims
7128       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7129       ics_user_moved = 1;
7130     }
7131   } else {
7132     if (first.sendTime && (gameMode == BeginningOfGame ||
7133                            gameMode == MachinePlaysWhite ||
7134                            gameMode == MachinePlaysBlack)) {
7135       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7136     }
7137     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7138          // [HGM] book: if program might be playing, let it use book
7139         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7140         first.maybeThinking = TRUE;
7141     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7142         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7143         SendBoard(&first, currentMove+1);
7144         if(second.analyzing) {
7145             if(!second.useSetboard) SendToProgram("undo\n", &second);
7146             SendBoard(&second, currentMove+1);
7147         }
7148     } else {
7149         SendMoveToProgram(forwardMostMove-1, &first);
7150         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7151     }
7152     if (currentMove == cmailOldMove + 1) {
7153       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7154     }
7155   }
7156
7157   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7158
7159   switch (gameMode) {
7160   case EditGame:
7161     if(appData.testLegality)
7162     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7163     case MT_NONE:
7164     case MT_CHECK:
7165       break;
7166     case MT_CHECKMATE:
7167     case MT_STAINMATE:
7168       if (WhiteOnMove(currentMove)) {
7169         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7170       } else {
7171         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7172       }
7173       break;
7174     case MT_STALEMATE:
7175       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7176       break;
7177     }
7178     break;
7179
7180   case MachinePlaysBlack:
7181   case MachinePlaysWhite:
7182     /* disable certain menu options while machine is thinking */
7183     SetMachineThinkingEnables();
7184     break;
7185
7186   default:
7187     break;
7188   }
7189
7190   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7191   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7192
7193   if(bookHit) { // [HGM] book: simulate book reply
7194         static char bookMove[MSG_SIZ]; // a bit generous?
7195
7196         programStats.nodes = programStats.depth = programStats.time =
7197         programStats.score = programStats.got_only_move = 0;
7198         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7199
7200         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7201         strcat(bookMove, bookHit);
7202         HandleMachineMove(bookMove, &first);
7203   }
7204   return 1;
7205 }
7206
7207 void
7208 MarkByFEN(char *fen)
7209 {
7210         int r, f;
7211         if(!appData.markers || !appData.highlightDragging) return;
7212         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7213         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7214         while(*fen) {
7215             int s = 0;
7216             marker[r][f] = 0;
7217             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7218             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7219             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7220             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7221             if(*fen == 'T') marker[r][f++] = 0; else
7222             if(*fen == 'Y') marker[r][f++] = 1; else
7223             if(*fen == 'G') marker[r][f++] = 3; else
7224             if(*fen == 'B') marker[r][f++] = 4; else
7225             if(*fen == 'C') marker[r][f++] = 5; else
7226             if(*fen == 'M') marker[r][f++] = 6; else
7227             if(*fen == 'W') marker[r][f++] = 7; else
7228             if(*fen == 'D') marker[r][f++] = 8; else
7229             if(*fen == 'R') marker[r][f++] = 2; else {
7230                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7231               f += s; fen -= s>0;
7232             }
7233             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7234             if(r < 0) break;
7235             fen++;
7236         }
7237         DrawPosition(TRUE, NULL);
7238 }
7239
7240 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7241
7242 void
7243 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7244 {
7245     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7246     Markers *m = (Markers *) closure;
7247     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7248         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7249                          || kind == WhiteCapturesEnPassant
7250                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7251     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7252 }
7253
7254 static int hoverSavedValid;
7255
7256 void
7257 MarkTargetSquares (int clear)
7258 {
7259   int x, y, sum=0;
7260   if(clear) { // no reason to ever suppress clearing
7261     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7262     hoverSavedValid = 0;
7263     if(!sum) return; // nothing was cleared,no redraw needed
7264   } else {
7265     int capt = 0;
7266     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7267        !appData.testLegality || gameMode == EditPosition) return;
7268     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7269     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7270       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7271       if(capt)
7272       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7273     }
7274   }
7275   DrawPosition(FALSE, NULL);
7276 }
7277
7278 int
7279 Explode (Board board, int fromX, int fromY, int toX, int toY)
7280 {
7281     if(gameInfo.variant == VariantAtomic &&
7282        (board[toY][toX] != EmptySquare ||                     // capture?
7283         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7284                          board[fromY][fromX] == BlackPawn   )
7285       )) {
7286         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7287         return TRUE;
7288     }
7289     return FALSE;
7290 }
7291
7292 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7293
7294 int
7295 CanPromote (ChessSquare piece, int y)
7296 {
7297         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7298         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7299         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7300         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7301            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7302            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7303          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7304         return (piece == BlackPawn && y <= zone ||
7305                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7306                 piece == BlackLance && y == 1 ||
7307                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7308 }
7309
7310 void
7311 HoverEvent (int xPix, int yPix, int x, int y)
7312 {
7313         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7314         int r, f;
7315         if(!first.highlight) return;
7316         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7317         if(x == oldX && y == oldY) return; // only do something if we enter new square
7318         oldFromX = fromX; oldFromY = fromY;
7319         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7320           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7321             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7322           hoverSavedValid = 1;
7323         } else if(oldX != x || oldY != y) {
7324           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7325           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7326           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7327             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7328           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7329             char buf[MSG_SIZ];
7330             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7331             SendToProgram(buf, &first);
7332           }
7333           oldX = x; oldY = y;
7334 //        SetHighlights(fromX, fromY, x, y);
7335         }
7336 }
7337
7338 void ReportClick(char *action, int x, int y)
7339 {
7340         char buf[MSG_SIZ]; // Inform engine of what user does
7341         int r, f;
7342         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7343           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7344         if(!first.highlight || gameMode == EditPosition) return;
7345         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7346         SendToProgram(buf, &first);
7347 }
7348
7349 void
7350 LeftClick (ClickType clickType, int xPix, int yPix)
7351 {
7352     int x, y;
7353     Boolean saveAnimate;
7354     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7355     char promoChoice = NULLCHAR;
7356     ChessSquare piece;
7357     static TimeMark lastClickTime, prevClickTime;
7358
7359     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7360
7361     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7362
7363     if (clickType == Press) ErrorPopDown();
7364     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7365
7366     x = EventToSquare(xPix, BOARD_WIDTH);
7367     y = EventToSquare(yPix, BOARD_HEIGHT);
7368     if (!flipView && y >= 0) {
7369         y = BOARD_HEIGHT - 1 - y;
7370     }
7371     if (flipView && x >= 0) {
7372         x = BOARD_WIDTH - 1 - x;
7373     }
7374
7375     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7376         defaultPromoChoice = promoSweep;
7377         promoSweep = EmptySquare;   // terminate sweep
7378         promoDefaultAltered = TRUE;
7379         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7380     }
7381
7382     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7383         if(clickType == Release) return; // ignore upclick of click-click destination
7384         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7385         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7386         if(gameInfo.holdingsWidth &&
7387                 (WhiteOnMove(currentMove)
7388                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7389                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7390             // click in right holdings, for determining promotion piece
7391             ChessSquare p = boards[currentMove][y][x];
7392             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7393             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7394             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7395                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7396                 fromX = fromY = -1;
7397                 return;
7398             }
7399         }
7400         DrawPosition(FALSE, boards[currentMove]);
7401         return;
7402     }
7403
7404     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7405     if(clickType == Press
7406             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7407               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7408               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7409         return;
7410
7411     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7412         // could be static click on premove from-square: abort premove
7413         gotPremove = 0;
7414         ClearPremoveHighlights();
7415     }
7416
7417     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7418         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7419
7420     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7421         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7422                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7423         defaultPromoChoice = DefaultPromoChoice(side);
7424     }
7425
7426     autoQueen = appData.alwaysPromoteToQueen;
7427
7428     if (fromX == -1) {
7429       int originalY = y;
7430       gatingPiece = EmptySquare;
7431       if (clickType != Press) {
7432         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7433             DragPieceEnd(xPix, yPix); dragging = 0;
7434             DrawPosition(FALSE, NULL);
7435         }
7436         return;
7437       }
7438       doubleClick = FALSE;
7439       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7440         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7441       }
7442       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7443       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7444          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7445          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7446             /* First square */
7447             if (OKToStartUserMove(fromX, fromY)) {
7448                 second = 0;
7449                 ReportClick("lift", x, y);
7450                 MarkTargetSquares(0);
7451                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7452                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7453                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7454                     promoSweep = defaultPromoChoice;
7455                     selectFlag = 0; lastX = xPix; lastY = yPix;
7456                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7457                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7458                 }
7459                 if (appData.highlightDragging) {
7460                     SetHighlights(fromX, fromY, -1, -1);
7461                 } else {
7462                     ClearHighlights();
7463                 }
7464             } else fromX = fromY = -1;
7465             return;
7466         }
7467     }
7468
7469     /* fromX != -1 */
7470     if (clickType == Press && gameMode != EditPosition) {
7471         ChessSquare fromP;
7472         ChessSquare toP;
7473         int frc;
7474
7475         // ignore off-board to clicks
7476         if(y < 0 || x < 0) return;
7477
7478         /* Check if clicking again on the same color piece */
7479         fromP = boards[currentMove][fromY][fromX];
7480         toP = boards[currentMove][y][x];
7481         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7482         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7483            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7484              WhitePawn <= toP && toP <= WhiteKing &&
7485              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7486              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7487             (BlackPawn <= fromP && fromP <= BlackKing &&
7488              BlackPawn <= toP && toP <= BlackKing &&
7489              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7490              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7491             /* Clicked again on same color piece -- changed his mind */
7492             second = (x == fromX && y == fromY);
7493             killX = killY = -1;
7494             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7495                 second = FALSE; // first double-click rather than scond click
7496                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7497             }
7498             promoDefaultAltered = FALSE;
7499             MarkTargetSquares(1);
7500            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7501             if (appData.highlightDragging) {
7502                 SetHighlights(x, y, -1, -1);
7503             } else {
7504                 ClearHighlights();
7505             }
7506             if (OKToStartUserMove(x, y)) {
7507                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7508                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7509                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7510                  gatingPiece = boards[currentMove][fromY][fromX];
7511                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7512                 fromX = x;
7513                 fromY = y; dragging = 1;
7514                 ReportClick("lift", x, y);
7515                 MarkTargetSquares(0);
7516                 DragPieceBegin(xPix, yPix, FALSE);
7517                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7518                     promoSweep = defaultPromoChoice;
7519                     selectFlag = 0; lastX = xPix; lastY = yPix;
7520                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7521                 }
7522             }
7523            }
7524            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7525            second = FALSE;
7526         }
7527         // ignore clicks on holdings
7528         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7529     }
7530
7531     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7532         DragPieceEnd(xPix, yPix); dragging = 0;
7533         if(clearFlag) {
7534             // a deferred attempt to click-click move an empty square on top of a piece
7535             boards[currentMove][y][x] = EmptySquare;
7536             ClearHighlights();
7537             DrawPosition(FALSE, boards[currentMove]);
7538             fromX = fromY = -1; clearFlag = 0;
7539             return;
7540         }
7541         if (appData.animateDragging) {
7542             /* Undo animation damage if any */
7543             DrawPosition(FALSE, NULL);
7544         }
7545         if (second || sweepSelecting) {
7546             /* Second up/down in same square; just abort move */
7547             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7548             second = sweepSelecting = 0;
7549             fromX = fromY = -1;
7550             gatingPiece = EmptySquare;
7551             MarkTargetSquares(1);
7552             ClearHighlights();
7553             gotPremove = 0;
7554             ClearPremoveHighlights();
7555         } else {
7556             /* First upclick in same square; start click-click mode */
7557             SetHighlights(x, y, -1, -1);
7558         }
7559         return;
7560     }
7561
7562     clearFlag = 0;
7563
7564     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7565         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7566         DisplayMessage(_("only marked squares are legal"),"");
7567         DrawPosition(TRUE, NULL);
7568         return; // ignore to-click
7569     }
7570
7571     /* we now have a different from- and (possibly off-board) to-square */
7572     /* Completed move */
7573     if(!sweepSelecting) {
7574         toX = x;
7575         toY = y;
7576     }
7577
7578     piece = boards[currentMove][fromY][fromX];
7579
7580     saveAnimate = appData.animate;
7581     if (clickType == Press) {
7582         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7583         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7584             // must be Edit Position mode with empty-square selected
7585             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7586             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7587             return;
7588         }
7589         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7590             return;
7591         }
7592         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7593             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7594         } else
7595         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7596         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7597           if(appData.sweepSelect) {
7598             promoSweep = defaultPromoChoice;
7599             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7600             selectFlag = 0; lastX = xPix; lastY = yPix;
7601             Sweep(0); // Pawn that is going to promote: preview promotion piece
7602             sweepSelecting = 1;
7603             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7604             MarkTargetSquares(1);
7605           }
7606           return; // promo popup appears on up-click
7607         }
7608         /* Finish clickclick move */
7609         if (appData.animate || appData.highlightLastMove) {
7610             SetHighlights(fromX, fromY, toX, toY);
7611         } else {
7612             ClearHighlights();
7613         }
7614     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7615         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7616         if (appData.animate || appData.highlightLastMove) {
7617             SetHighlights(fromX, fromY, toX, toY);
7618         } else {
7619             ClearHighlights();
7620         }
7621     } else {
7622 #if 0
7623 // [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
7624         /* Finish drag move */
7625         if (appData.highlightLastMove) {
7626             SetHighlights(fromX, fromY, toX, toY);
7627         } else {
7628             ClearHighlights();
7629         }
7630 #endif
7631         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7632         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7633           dragging *= 2;            // flag button-less dragging if we are dragging
7634           MarkTargetSquares(1);
7635           if(x == killX && y == killY) killX = killY = -1; else {
7636             killX = x; killY = y;     //remeber this square as intermediate
7637             ReportClick("put", x, y); // and inform engine
7638             ReportClick("lift", x, y);
7639             MarkTargetSquares(0);
7640             return;
7641           }
7642         }
7643         DragPieceEnd(xPix, yPix); dragging = 0;
7644         /* Don't animate move and drag both */
7645         appData.animate = FALSE;
7646     }
7647
7648     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7649     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7650         ChessSquare piece = boards[currentMove][fromY][fromX];
7651         if(gameMode == EditPosition && piece != EmptySquare &&
7652            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7653             int n;
7654
7655             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7656                 n = PieceToNumber(piece - (int)BlackPawn);
7657                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7658                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7659                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7660             } else
7661             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7662                 n = PieceToNumber(piece);
7663                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7664                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7665                 boards[currentMove][n][BOARD_WIDTH-2]++;
7666             }
7667             boards[currentMove][fromY][fromX] = EmptySquare;
7668         }
7669         ClearHighlights();
7670         fromX = fromY = -1;
7671         MarkTargetSquares(1);
7672         DrawPosition(TRUE, boards[currentMove]);
7673         return;
7674     }
7675
7676     // off-board moves should not be highlighted
7677     if(x < 0 || y < 0) ClearHighlights();
7678     else ReportClick("put", x, y);
7679
7680     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7681
7682     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7683         SetHighlights(fromX, fromY, toX, toY);
7684         MarkTargetSquares(1);
7685         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7686             // [HGM] super: promotion to captured piece selected from holdings
7687             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7688             promotionChoice = TRUE;
7689             // kludge follows to temporarily execute move on display, without promoting yet
7690             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7691             boards[currentMove][toY][toX] = p;
7692             DrawPosition(FALSE, boards[currentMove]);
7693             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7694             boards[currentMove][toY][toX] = q;
7695             DisplayMessage("Click in holdings to choose piece", "");
7696             return;
7697         }
7698         PromotionPopUp(promoChoice);
7699     } else {
7700         int oldMove = currentMove;
7701         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7702         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7703         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7704         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7705            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7706             DrawPosition(TRUE, boards[currentMove]);
7707         MarkTargetSquares(1);
7708         fromX = fromY = -1;
7709     }
7710     appData.animate = saveAnimate;
7711     if (appData.animate || appData.animateDragging) {
7712         /* Undo animation damage if needed */
7713         DrawPosition(FALSE, NULL);
7714     }
7715 }
7716
7717 int
7718 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7719 {   // front-end-free part taken out of PieceMenuPopup
7720     int whichMenu; int xSqr, ySqr;
7721
7722     if(seekGraphUp) { // [HGM] seekgraph
7723         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7724         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7725         return -2;
7726     }
7727
7728     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7729          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7730         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7731         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7732         if(action == Press)   {
7733             originalFlip = flipView;
7734             flipView = !flipView; // temporarily flip board to see game from partners perspective
7735             DrawPosition(TRUE, partnerBoard);
7736             DisplayMessage(partnerStatus, "");
7737             partnerUp = TRUE;
7738         } else if(action == Release) {
7739             flipView = originalFlip;
7740             DrawPosition(TRUE, boards[currentMove]);
7741             partnerUp = FALSE;
7742         }
7743         return -2;
7744     }
7745
7746     xSqr = EventToSquare(x, BOARD_WIDTH);
7747     ySqr = EventToSquare(y, BOARD_HEIGHT);
7748     if (action == Release) {
7749         if(pieceSweep != EmptySquare) {
7750             EditPositionMenuEvent(pieceSweep, toX, toY);
7751             pieceSweep = EmptySquare;
7752         } else UnLoadPV(); // [HGM] pv
7753     }
7754     if (action != Press) return -2; // return code to be ignored
7755     switch (gameMode) {
7756       case IcsExamining:
7757         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7758       case EditPosition:
7759         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7760         if (xSqr < 0 || ySqr < 0) return -1;
7761         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7762         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7763         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7764         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7765         NextPiece(0);
7766         return 2; // grab
7767       case IcsObserving:
7768         if(!appData.icsEngineAnalyze) return -1;
7769       case IcsPlayingWhite:
7770       case IcsPlayingBlack:
7771         if(!appData.zippyPlay) goto noZip;
7772       case AnalyzeMode:
7773       case AnalyzeFile:
7774       case MachinePlaysWhite:
7775       case MachinePlaysBlack:
7776       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7777         if (!appData.dropMenu) {
7778           LoadPV(x, y);
7779           return 2; // flag front-end to grab mouse events
7780         }
7781         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7782            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7783       case EditGame:
7784       noZip:
7785         if (xSqr < 0 || ySqr < 0) return -1;
7786         if (!appData.dropMenu || appData.testLegality &&
7787             gameInfo.variant != VariantBughouse &&
7788             gameInfo.variant != VariantCrazyhouse) return -1;
7789         whichMenu = 1; // drop menu
7790         break;
7791       default:
7792         return -1;
7793     }
7794
7795     if (((*fromX = xSqr) < 0) ||
7796         ((*fromY = ySqr) < 0)) {
7797         *fromX = *fromY = -1;
7798         return -1;
7799     }
7800     if (flipView)
7801       *fromX = BOARD_WIDTH - 1 - *fromX;
7802     else
7803       *fromY = BOARD_HEIGHT - 1 - *fromY;
7804
7805     return whichMenu;
7806 }
7807
7808 void
7809 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7810 {
7811 //    char * hint = lastHint;
7812     FrontEndProgramStats stats;
7813
7814     stats.which = cps == &first ? 0 : 1;
7815     stats.depth = cpstats->depth;
7816     stats.nodes = cpstats->nodes;
7817     stats.score = cpstats->score;
7818     stats.time = cpstats->time;
7819     stats.pv = cpstats->movelist;
7820     stats.hint = lastHint;
7821     stats.an_move_index = 0;
7822     stats.an_move_count = 0;
7823
7824     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7825         stats.hint = cpstats->move_name;
7826         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7827         stats.an_move_count = cpstats->nr_moves;
7828     }
7829
7830     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
7831
7832     SetProgramStats( &stats );
7833 }
7834
7835 void
7836 ClearEngineOutputPane (int which)
7837 {
7838     static FrontEndProgramStats dummyStats;
7839     dummyStats.which = which;
7840     dummyStats.pv = "#";
7841     SetProgramStats( &dummyStats );
7842 }
7843
7844 #define MAXPLAYERS 500
7845
7846 char *
7847 TourneyStandings (int display)
7848 {
7849     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7850     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7851     char result, *p, *names[MAXPLAYERS];
7852
7853     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7854         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7855     names[0] = p = strdup(appData.participants);
7856     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7857
7858     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7859
7860     while(result = appData.results[nr]) {
7861         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7862         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7863         wScore = bScore = 0;
7864         switch(result) {
7865           case '+': wScore = 2; break;
7866           case '-': bScore = 2; break;
7867           case '=': wScore = bScore = 1; break;
7868           case ' ':
7869           case '*': return strdup("busy"); // tourney not finished
7870         }
7871         score[w] += wScore;
7872         score[b] += bScore;
7873         games[w]++;
7874         games[b]++;
7875         nr++;
7876     }
7877     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7878     for(w=0; w<nPlayers; w++) {
7879         bScore = -1;
7880         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7881         ranking[w] = b; points[w] = bScore; score[b] = -2;
7882     }
7883     p = malloc(nPlayers*34+1);
7884     for(w=0; w<nPlayers && w<display; w++)
7885         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7886     free(names[0]);
7887     return p;
7888 }
7889
7890 void
7891 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7892 {       // count all piece types
7893         int p, f, r;
7894         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7895         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7896         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7897                 p = board[r][f];
7898                 pCnt[p]++;
7899                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7900                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7901                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7902                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7903                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7904                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7905         }
7906 }
7907
7908 int
7909 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7910 {
7911         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7912         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7913
7914         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7915         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7916         if(myPawns == 2 && nMine == 3) // KPP
7917             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7918         if(myPawns == 1 && nMine == 2) // KP
7919             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7920         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7921             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7922         if(myPawns) return FALSE;
7923         if(pCnt[WhiteRook+side])
7924             return pCnt[BlackRook-side] ||
7925                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7926                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7927                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7928         if(pCnt[WhiteCannon+side]) {
7929             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7930             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7931         }
7932         if(pCnt[WhiteKnight+side])
7933             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7934         return FALSE;
7935 }
7936
7937 int
7938 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7939 {
7940         VariantClass v = gameInfo.variant;
7941
7942         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7943         if(v == VariantShatranj) return TRUE; // always winnable through baring
7944         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7945         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7946
7947         if(v == VariantXiangqi) {
7948                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7949
7950                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7951                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7952                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7953                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7954                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7955                 if(stale) // we have at least one last-rank P plus perhaps C
7956                     return majors // KPKX
7957                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7958                 else // KCA*E*
7959                     return pCnt[WhiteFerz+side] // KCAK
7960                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7961                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7962                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7963
7964         } else if(v == VariantKnightmate) {
7965                 if(nMine == 1) return FALSE;
7966                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7967         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7968                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7969
7970                 if(nMine == 1) return FALSE; // bare King
7971                 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
7972                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7973                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7974                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7975                 if(pCnt[WhiteKnight+side])
7976                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7977                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7978                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7979                 if(nBishops)
7980                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7981                 if(pCnt[WhiteAlfil+side])
7982                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7983                 if(pCnt[WhiteWazir+side])
7984                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7985         }
7986
7987         return TRUE;
7988 }
7989
7990 int
7991 CompareWithRights (Board b1, Board b2)
7992 {
7993     int rights = 0;
7994     if(!CompareBoards(b1, b2)) return FALSE;
7995     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7996     /* compare castling rights */
7997     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7998            rights++; /* King lost rights, while rook still had them */
7999     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8000         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8001            rights++; /* but at least one rook lost them */
8002     }
8003     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8004            rights++;
8005     if( b1[CASTLING][5] != NoRights ) {
8006         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8007            rights++;
8008     }
8009     return rights == 0;
8010 }
8011
8012 int
8013 Adjudicate (ChessProgramState *cps)
8014 {       // [HGM] some adjudications useful with buggy engines
8015         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8016         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8017         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8018         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8019         int k, drop, count = 0; static int bare = 1;
8020         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8021         Boolean canAdjudicate = !appData.icsActive;
8022
8023         // most tests only when we understand the game, i.e. legality-checking on
8024             if( appData.testLegality )
8025             {   /* [HGM] Some more adjudications for obstinate engines */
8026                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8027                 static int moveCount = 6;
8028                 ChessMove result;
8029                 char *reason = NULL;
8030
8031                 /* Count what is on board. */
8032                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8033
8034                 /* Some material-based adjudications that have to be made before stalemate test */
8035                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8036                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8037                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8038                      if(canAdjudicate && appData.checkMates) {
8039                          if(engineOpponent)
8040                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8041                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8042                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8043                          return 1;
8044                      }
8045                 }
8046
8047                 /* Bare King in Shatranj (loses) or Losers (wins) */
8048                 if( nrW == 1 || nrB == 1) {
8049                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8050                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8051                      if(canAdjudicate && appData.checkMates) {
8052                          if(engineOpponent)
8053                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8054                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8055                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8056                          return 1;
8057                      }
8058                   } else
8059                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8060                   {    /* bare King */
8061                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8062                         if(canAdjudicate && appData.checkMates) {
8063                             /* but only adjudicate if adjudication enabled */
8064                             if(engineOpponent)
8065                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8066                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8067                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8068                             return 1;
8069                         }
8070                   }
8071                 } else bare = 1;
8072
8073
8074             // don't wait for engine to announce game end if we can judge ourselves
8075             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8076               case MT_CHECK:
8077                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8078                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8079                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8080                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8081                             checkCnt++;
8082                         if(checkCnt >= 2) {
8083                             reason = "Xboard adjudication: 3rd check";
8084                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8085                             break;
8086                         }
8087                     }
8088                 }
8089               case MT_NONE:
8090               default:
8091                 break;
8092               case MT_STEALMATE:
8093               case MT_STALEMATE:
8094               case MT_STAINMATE:
8095                 reason = "Xboard adjudication: Stalemate";
8096                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8097                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8098                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8099                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8100                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8101                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8102                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8103                                                                         EP_CHECKMATE : EP_WINS);
8104                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8105                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8106                 }
8107                 break;
8108               case MT_CHECKMATE:
8109                 reason = "Xboard adjudication: Checkmate";
8110                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8111                 if(gameInfo.variant == VariantShogi) {
8112                     if(forwardMostMove > backwardMostMove
8113                        && moveList[forwardMostMove-1][1] == '@'
8114                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8115                         reason = "XBoard adjudication: pawn-drop mate";
8116                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8117                     }
8118                 }
8119                 break;
8120             }
8121
8122                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8123                     case EP_STALEMATE:
8124                         result = GameIsDrawn; break;
8125                     case EP_CHECKMATE:
8126                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8127                     case EP_WINS:
8128                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8129                     default:
8130                         result = EndOfFile;
8131                 }
8132                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8133                     if(engineOpponent)
8134                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8135                     GameEnds( result, reason, GE_XBOARD );
8136                     return 1;
8137                 }
8138
8139                 /* Next absolutely insufficient mating material. */
8140                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8141                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8142                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8143
8144                      /* always flag draws, for judging claims */
8145                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8146
8147                      if(canAdjudicate && appData.materialDraws) {
8148                          /* but only adjudicate them if adjudication enabled */
8149                          if(engineOpponent) {
8150                            SendToProgram("force\n", engineOpponent); // suppress reply
8151                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8152                          }
8153                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8154                          return 1;
8155                      }
8156                 }
8157
8158                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8159                 if(gameInfo.variant == VariantXiangqi ?
8160                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8161                  : nrW + nrB == 4 &&
8162                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8163                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8164                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8165                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8166                    ) ) {
8167                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8168                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8169                           if(engineOpponent) {
8170                             SendToProgram("force\n", engineOpponent); // suppress reply
8171                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8172                           }
8173                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8174                           return 1;
8175                      }
8176                 } else moveCount = 6;
8177             }
8178
8179         // Repetition draws and 50-move rule can be applied independently of legality testing
8180
8181                 /* Check for rep-draws */
8182                 count = 0;
8183                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8184                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8185                 for(k = forwardMostMove-2;
8186                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8187                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8188                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8189                     k-=2)
8190                 {   int rights=0;
8191                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8192                         /* compare castling rights */
8193                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8194                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8195                                 rights++; /* King lost rights, while rook still had them */
8196                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8197                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8198                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8199                                    rights++; /* but at least one rook lost them */
8200                         }
8201                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8202                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8203                                 rights++;
8204                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8205                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8206                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8207                                    rights++;
8208                         }
8209                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8210                             && appData.drawRepeats > 1) {
8211                              /* adjudicate after user-specified nr of repeats */
8212                              int result = GameIsDrawn;
8213                              char *details = "XBoard adjudication: repetition draw";
8214                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8215                                 // [HGM] xiangqi: check for forbidden perpetuals
8216                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8217                                 for(m=forwardMostMove; m>k; m-=2) {
8218                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8219                                         ourPerpetual = 0; // the current mover did not always check
8220                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8221                                         hisPerpetual = 0; // the opponent did not always check
8222                                 }
8223                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8224                                                                         ourPerpetual, hisPerpetual);
8225                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8226                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8227                                     details = "Xboard adjudication: perpetual checking";
8228                                 } else
8229                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8230                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8231                                 } else
8232                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8233                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8234                                         result = BlackWins;
8235                                         details = "Xboard adjudication: repetition";
8236                                     }
8237                                 } else // it must be XQ
8238                                 // Now check for perpetual chases
8239                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8240                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8241                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8242                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8243                                         static char resdet[MSG_SIZ];
8244                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8245                                         details = resdet;
8246                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8247                                     } else
8248                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8249                                         break; // Abort repetition-checking loop.
8250                                 }
8251                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8252                              }
8253                              if(engineOpponent) {
8254                                SendToProgram("force\n", engineOpponent); // suppress reply
8255                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8256                              }
8257                              GameEnds( result, details, GE_XBOARD );
8258                              return 1;
8259                         }
8260                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8261                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8262                     }
8263                 }
8264
8265                 /* Now we test for 50-move draws. Determine ply count */
8266                 count = forwardMostMove;
8267                 /* look for last irreversble move */
8268                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8269                     count--;
8270                 /* if we hit starting position, add initial plies */
8271                 if( count == backwardMostMove )
8272                     count -= initialRulePlies;
8273                 count = forwardMostMove - count;
8274                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8275                         // adjust reversible move counter for checks in Xiangqi
8276                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8277                         if(i < backwardMostMove) i = backwardMostMove;
8278                         while(i <= forwardMostMove) {
8279                                 lastCheck = inCheck; // check evasion does not count
8280                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8281                                 if(inCheck || lastCheck) count--; // check does not count
8282                                 i++;
8283                         }
8284                 }
8285                 if( count >= 100)
8286                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8287                          /* this is used to judge if draw claims are legal */
8288                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8289                          if(engineOpponent) {
8290                            SendToProgram("force\n", engineOpponent); // suppress reply
8291                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8292                          }
8293                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8294                          return 1;
8295                 }
8296
8297                 /* if draw offer is pending, treat it as a draw claim
8298                  * when draw condition present, to allow engines a way to
8299                  * claim draws before making their move to avoid a race
8300                  * condition occurring after their move
8301                  */
8302                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8303                          char *p = NULL;
8304                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8305                              p = "Draw claim: 50-move rule";
8306                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8307                              p = "Draw claim: 3-fold repetition";
8308                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8309                              p = "Draw claim: insufficient mating material";
8310                          if( p != NULL && canAdjudicate) {
8311                              if(engineOpponent) {
8312                                SendToProgram("force\n", engineOpponent); // suppress reply
8313                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8314                              }
8315                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8316                              return 1;
8317                          }
8318                 }
8319
8320                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8321                     if(engineOpponent) {
8322                       SendToProgram("force\n", engineOpponent); // suppress reply
8323                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8324                     }
8325                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8326                     return 1;
8327                 }
8328         return 0;
8329 }
8330
8331 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8332 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8333 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8334
8335 static int
8336 BitbaseProbe ()
8337 {
8338     int pieces[10], squares[10], cnt=0, r, f, res;
8339     static int loaded;
8340     static PPROBE_EGBB probeBB;
8341     if(!appData.testLegality) return 10;
8342     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8343     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8344     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8345     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8346         ChessSquare piece = boards[forwardMostMove][r][f];
8347         int black = (piece >= BlackPawn);
8348         int type = piece - black*BlackPawn;
8349         if(piece == EmptySquare) continue;
8350         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8351         if(type == WhiteKing) type = WhiteQueen + 1;
8352         type = egbbCode[type];
8353         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8354         pieces[cnt] = type + black*6;
8355         if(++cnt > 5) return 11;
8356     }
8357     pieces[cnt] = squares[cnt] = 0;
8358     // probe EGBB
8359     if(loaded == 2) return 13; // loading failed before
8360     if(loaded == 0) {
8361         loaded = 2; // prepare for failure
8362         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8363         HMODULE lib;
8364         PLOAD_EGBB loadBB;
8365         if(!path) return 13; // no egbb installed
8366         strncpy(buf, path + 8, MSG_SIZ);
8367         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8368         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8369         lib = LoadLibrary(buf);
8370         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8371         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8372         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8373         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8374         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8375         loaded = 1; // success!
8376     }
8377     res = probeBB(forwardMostMove & 1, pieces, squares);
8378     return res > 0 ? 1 : res < 0 ? -1 : 0;
8379 }
8380
8381 char *
8382 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8383 {   // [HGM] book: this routine intercepts moves to simulate book replies
8384     char *bookHit = NULL;
8385
8386     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8387         char buf[MSG_SIZ];
8388         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8389         SendToProgram(buf, cps);
8390     }
8391     //first determine if the incoming move brings opponent into his book
8392     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8393         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8394     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8395     if(bookHit != NULL && !cps->bookSuspend) {
8396         // make sure opponent is not going to reply after receiving move to book position
8397         SendToProgram("force\n", cps);
8398         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8399     }
8400     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8401     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8402     // now arrange restart after book miss
8403     if(bookHit) {
8404         // after a book hit we never send 'go', and the code after the call to this routine
8405         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8406         char buf[MSG_SIZ], *move = bookHit;
8407         if(cps->useSAN) {
8408             int fromX, fromY, toX, toY;
8409             char promoChar;
8410             ChessMove moveType;
8411             move = buf + 30;
8412             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8413                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8414                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8415                                     PosFlags(forwardMostMove),
8416                                     fromY, fromX, toY, toX, promoChar, move);
8417             } else {
8418                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8419                 bookHit = NULL;
8420             }
8421         }
8422         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8423         SendToProgram(buf, cps);
8424         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8425     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8426         SendToProgram("go\n", cps);
8427         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8428     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8429         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8430             SendToProgram("go\n", cps);
8431         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8432     }
8433     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8434 }
8435
8436 int
8437 LoadError (char *errmess, ChessProgramState *cps)
8438 {   // unloads engine and switches back to -ncp mode if it was first
8439     if(cps->initDone) return FALSE;
8440     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8441     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8442     cps->pr = NoProc;
8443     if(cps == &first) {
8444         appData.noChessProgram = TRUE;
8445         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8446         gameMode = BeginningOfGame; ModeHighlight();
8447         SetNCPMode();
8448     }
8449     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8450     DisplayMessage("", ""); // erase waiting message
8451     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8452     return TRUE;
8453 }
8454
8455 char *savedMessage;
8456 ChessProgramState *savedState;
8457 void
8458 DeferredBookMove (void)
8459 {
8460         if(savedState->lastPing != savedState->lastPong)
8461                     ScheduleDelayedEvent(DeferredBookMove, 10);
8462         else
8463         HandleMachineMove(savedMessage, savedState);
8464 }
8465
8466 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8467 static ChessProgramState *stalledEngine;
8468 static char stashedInputMove[MSG_SIZ];
8469
8470 void
8471 HandleMachineMove (char *message, ChessProgramState *cps)
8472 {
8473     static char firstLeg[20];
8474     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8475     char realname[MSG_SIZ];
8476     int fromX, fromY, toX, toY;
8477     ChessMove moveType;
8478     char promoChar, roar;
8479     char *p, *pv=buf1;
8480     int machineWhite, oldError;
8481     char *bookHit;
8482
8483     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8484         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8485         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8486             DisplayError(_("Invalid pairing from pairing engine"), 0);
8487             return;
8488         }
8489         pairingReceived = 1;
8490         NextMatchGame();
8491         return; // Skim the pairing messages here.
8492     }
8493
8494     oldError = cps->userError; cps->userError = 0;
8495
8496 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8497     /*
8498      * Kludge to ignore BEL characters
8499      */
8500     while (*message == '\007') message++;
8501
8502     /*
8503      * [HGM] engine debug message: ignore lines starting with '#' character
8504      */
8505     if(cps->debug && *message == '#') return;
8506
8507     /*
8508      * Look for book output
8509      */
8510     if (cps == &first && bookRequested) {
8511         if (message[0] == '\t' || message[0] == ' ') {
8512             /* Part of the book output is here; append it */
8513             strcat(bookOutput, message);
8514             strcat(bookOutput, "  \n");
8515             return;
8516         } else if (bookOutput[0] != NULLCHAR) {
8517             /* All of book output has arrived; display it */
8518             char *p = bookOutput;
8519             while (*p != NULLCHAR) {
8520                 if (*p == '\t') *p = ' ';
8521                 p++;
8522             }
8523             DisplayInformation(bookOutput);
8524             bookRequested = FALSE;
8525             /* Fall through to parse the current output */
8526         }
8527     }
8528
8529     /*
8530      * Look for machine move.
8531      */
8532     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8533         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8534     {
8535         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8536             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8537             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8538             stalledEngine = cps;
8539             if(appData.ponderNextMove) { // bring opponent out of ponder
8540                 if(gameMode == TwoMachinesPlay) {
8541                     if(cps->other->pause)
8542                         PauseEngine(cps->other);
8543                     else
8544                         SendToProgram("easy\n", cps->other);
8545                 }
8546             }
8547             StopClocks();
8548             return;
8549         }
8550
8551         /* This method is only useful on engines that support ping */
8552         if (cps->lastPing != cps->lastPong) {
8553           if (gameMode == BeginningOfGame) {
8554             /* Extra move from before last new; ignore */
8555             if (appData.debugMode) {
8556                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8557             }
8558           } else {
8559             if (appData.debugMode) {
8560                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8561                         cps->which, gameMode);
8562             }
8563
8564             SendToProgram("undo\n", cps);
8565           }
8566           return;
8567         }
8568
8569         switch (gameMode) {
8570           case BeginningOfGame:
8571             /* Extra move from before last reset; ignore */
8572             if (appData.debugMode) {
8573                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8574             }
8575             return;
8576
8577           case EndOfGame:
8578           case IcsIdle:
8579           default:
8580             /* Extra move after we tried to stop.  The mode test is
8581                not a reliable way of detecting this problem, but it's
8582                the best we can do on engines that don't support ping.
8583             */
8584             if (appData.debugMode) {
8585                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8586                         cps->which, gameMode);
8587             }
8588             SendToProgram("undo\n", cps);
8589             return;
8590
8591           case MachinePlaysWhite:
8592           case IcsPlayingWhite:
8593             machineWhite = TRUE;
8594             break;
8595
8596           case MachinePlaysBlack:
8597           case IcsPlayingBlack:
8598             machineWhite = FALSE;
8599             break;
8600
8601           case TwoMachinesPlay:
8602             machineWhite = (cps->twoMachinesColor[0] == 'w');
8603             break;
8604         }
8605         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8606             if (appData.debugMode) {
8607                 fprintf(debugFP,
8608                         "Ignoring move out of turn by %s, gameMode %d"
8609                         ", forwardMost %d\n",
8610                         cps->which, gameMode, forwardMostMove);
8611             }
8612             return;
8613         }
8614
8615         if(cps->alphaRank) AlphaRank(machineMove, 4);
8616
8617         // [HGM] lion: (some very limited) support for Alien protocol
8618         killX = killY = -1;
8619         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8620             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8621             return;
8622         } else if(firstLeg[0]) { // there was a previous leg;
8623             // only support case where same piece makes two step (and don't even test that!)
8624             char buf[20], *p = machineMove+1, *q = buf+1, f;
8625             safeStrCpy(buf, machineMove, 20);
8626             while(isdigit(*q)) q++; // find start of to-square
8627             safeStrCpy(machineMove, firstLeg, 20);
8628             while(isdigit(*p)) p++;
8629             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8630             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8631             firstLeg[0] = NULLCHAR;
8632         }
8633
8634         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8635                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8636             /* Machine move could not be parsed; ignore it. */
8637           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8638                     machineMove, _(cps->which));
8639             DisplayMoveError(buf1);
8640             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8641                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8642             if (gameMode == TwoMachinesPlay) {
8643               GameEnds(machineWhite ? BlackWins : WhiteWins,
8644                        buf1, GE_XBOARD);
8645             }
8646             return;
8647         }
8648
8649         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8650         /* So we have to redo legality test with true e.p. status here,  */
8651         /* to make sure an illegal e.p. capture does not slip through,   */
8652         /* to cause a forfeit on a justified illegal-move complaint      */
8653         /* of the opponent.                                              */
8654         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8655            ChessMove moveType;
8656            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8657                              fromY, fromX, toY, toX, promoChar);
8658             if(moveType == IllegalMove) {
8659               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8660                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8661                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8662                            buf1, GE_XBOARD);
8663                 return;
8664            } else if(!appData.fischerCastling)
8665            /* [HGM] Kludge to handle engines that send FRC-style castling
8666               when they shouldn't (like TSCP-Gothic) */
8667            switch(moveType) {
8668              case WhiteASideCastleFR:
8669              case BlackASideCastleFR:
8670                toX+=2;
8671                currentMoveString[2]++;
8672                break;
8673              case WhiteHSideCastleFR:
8674              case BlackHSideCastleFR:
8675                toX--;
8676                currentMoveString[2]--;
8677                break;
8678              default: ; // nothing to do, but suppresses warning of pedantic compilers
8679            }
8680         }
8681         hintRequested = FALSE;
8682         lastHint[0] = NULLCHAR;
8683         bookRequested = FALSE;
8684         /* Program may be pondering now */
8685         cps->maybeThinking = TRUE;
8686         if (cps->sendTime == 2) cps->sendTime = 1;
8687         if (cps->offeredDraw) cps->offeredDraw--;
8688
8689         /* [AS] Save move info*/
8690         pvInfoList[ forwardMostMove ].score = programStats.score;
8691         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8692         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8693
8694         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8695
8696         /* Test suites abort the 'game' after one move */
8697         if(*appData.finger) {
8698            static FILE *f;
8699            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8700            if(!f) f = fopen(appData.finger, "w");
8701            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8702            else { DisplayFatalError("Bad output file", errno, 0); return; }
8703            free(fen);
8704            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8705         }
8706
8707         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8708         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8709             int count = 0;
8710
8711             while( count < adjudicateLossPlies ) {
8712                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8713
8714                 if( count & 1 ) {
8715                     score = -score; /* Flip score for winning side */
8716                 }
8717 printf("score=%d count=%d\n",score,count);
8718                 if( score > appData.adjudicateLossThreshold ) {
8719                     break;
8720                 }
8721
8722                 count++;
8723             }
8724
8725             if( count >= adjudicateLossPlies ) {
8726                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8727
8728                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8729                     "Xboard adjudication",
8730                     GE_XBOARD );
8731
8732                 return;
8733             }
8734         }
8735
8736         if(Adjudicate(cps)) {
8737             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8738             return; // [HGM] adjudicate: for all automatic game ends
8739         }
8740
8741 #if ZIPPY
8742         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8743             first.initDone) {
8744           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8745                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8746                 SendToICS("draw ");
8747                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8748           }
8749           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8750           ics_user_moved = 1;
8751           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8752                 char buf[3*MSG_SIZ];
8753
8754                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8755                         programStats.score / 100.,
8756                         programStats.depth,
8757                         programStats.time / 100.,
8758                         (unsigned int)programStats.nodes,
8759                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8760                         programStats.movelist);
8761                 SendToICS(buf);
8762           }
8763         }
8764 #endif
8765
8766         /* [AS] Clear stats for next move */
8767         ClearProgramStats();
8768         thinkOutput[0] = NULLCHAR;
8769         hiddenThinkOutputState = 0;
8770
8771         bookHit = NULL;
8772         if (gameMode == TwoMachinesPlay) {
8773             /* [HGM] relaying draw offers moved to after reception of move */
8774             /* and interpreting offer as claim if it brings draw condition */
8775             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8776                 SendToProgram("draw\n", cps->other);
8777             }
8778             if (cps->other->sendTime) {
8779                 SendTimeRemaining(cps->other,
8780                                   cps->other->twoMachinesColor[0] == 'w');
8781             }
8782             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8783             if (firstMove && !bookHit) {
8784                 firstMove = FALSE;
8785                 if (cps->other->useColors) {
8786                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8787                 }
8788                 SendToProgram("go\n", cps->other);
8789             }
8790             cps->other->maybeThinking = TRUE;
8791         }
8792
8793         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8794
8795         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8796
8797         if (!pausing && appData.ringBellAfterMoves) {
8798             if(!roar) RingBell();
8799         }
8800
8801         /*
8802          * Reenable menu items that were disabled while
8803          * machine was thinking
8804          */
8805         if (gameMode != TwoMachinesPlay)
8806             SetUserThinkingEnables();
8807
8808         // [HGM] book: after book hit opponent has received move and is now in force mode
8809         // force the book reply into it, and then fake that it outputted this move by jumping
8810         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8811         if(bookHit) {
8812                 static char bookMove[MSG_SIZ]; // a bit generous?
8813
8814                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8815                 strcat(bookMove, bookHit);
8816                 message = bookMove;
8817                 cps = cps->other;
8818                 programStats.nodes = programStats.depth = programStats.time =
8819                 programStats.score = programStats.got_only_move = 0;
8820                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8821
8822                 if(cps->lastPing != cps->lastPong) {
8823                     savedMessage = message; // args for deferred call
8824                     savedState = cps;
8825                     ScheduleDelayedEvent(DeferredBookMove, 10);
8826                     return;
8827                 }
8828                 goto FakeBookMove;
8829         }
8830
8831         return;
8832     }
8833
8834     /* Set special modes for chess engines.  Later something general
8835      *  could be added here; for now there is just one kludge feature,
8836      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8837      *  when "xboard" is given as an interactive command.
8838      */
8839     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8840         cps->useSigint = FALSE;
8841         cps->useSigterm = FALSE;
8842     }
8843     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8844       ParseFeatures(message+8, cps);
8845       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8846     }
8847
8848     if (!strncmp(message, "setup ", 6) && 
8849         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8850           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8851                                         ) { // [HGM] allow first engine to define opening position
8852       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8853       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8854       *buf = NULLCHAR;
8855       if(sscanf(message, "setup (%s", buf) == 1) {
8856         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8857         ASSIGN(appData.pieceToCharTable, buf);
8858       }
8859       if(startedFromSetupPosition) return;
8860       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8861       if(dummy >= 3) {
8862         while(message[s] && message[s++] != ' ');
8863         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8864            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8865             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8866             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8867           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8868           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8869         }
8870       }
8871       ParseFEN(boards[0], &dummy, message+s, FALSE);
8872       DrawPosition(TRUE, boards[0]);
8873       startedFromSetupPosition = TRUE;
8874       return;
8875     }
8876     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8877      * want this, I was asked to put it in, and obliged.
8878      */
8879     if (!strncmp(message, "setboard ", 9)) {
8880         Board initial_position;
8881
8882         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8883
8884         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8885             DisplayError(_("Bad FEN received from engine"), 0);
8886             return ;
8887         } else {
8888            Reset(TRUE, FALSE);
8889            CopyBoard(boards[0], initial_position);
8890            initialRulePlies = FENrulePlies;
8891            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8892            else gameMode = MachinePlaysBlack;
8893            DrawPosition(FALSE, boards[currentMove]);
8894         }
8895         return;
8896     }
8897
8898     /*
8899      * Look for communication commands
8900      */
8901     if (!strncmp(message, "telluser ", 9)) {
8902         if(message[9] == '\\' && message[10] == '\\')
8903             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8904         PlayTellSound();
8905         DisplayNote(message + 9);
8906         return;
8907     }
8908     if (!strncmp(message, "tellusererror ", 14)) {
8909         cps->userError = 1;
8910         if(message[14] == '\\' && message[15] == '\\')
8911             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8912         PlayTellSound();
8913         DisplayError(message + 14, 0);
8914         return;
8915     }
8916     if (!strncmp(message, "tellopponent ", 13)) {
8917       if (appData.icsActive) {
8918         if (loggedOn) {
8919           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8920           SendToICS(buf1);
8921         }
8922       } else {
8923         DisplayNote(message + 13);
8924       }
8925       return;
8926     }
8927     if (!strncmp(message, "tellothers ", 11)) {
8928       if (appData.icsActive) {
8929         if (loggedOn) {
8930           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8931           SendToICS(buf1);
8932         }
8933       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8934       return;
8935     }
8936     if (!strncmp(message, "tellall ", 8)) {
8937       if (appData.icsActive) {
8938         if (loggedOn) {
8939           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8940           SendToICS(buf1);
8941         }
8942       } else {
8943         DisplayNote(message + 8);
8944       }
8945       return;
8946     }
8947     if (strncmp(message, "warning", 7) == 0) {
8948         /* Undocumented feature, use tellusererror in new code */
8949         DisplayError(message, 0);
8950         return;
8951     }
8952     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8953         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8954         strcat(realname, " query");
8955         AskQuestion(realname, buf2, buf1, cps->pr);
8956         return;
8957     }
8958     /* Commands from the engine directly to ICS.  We don't allow these to be
8959      *  sent until we are logged on. Crafty kibitzes have been known to
8960      *  interfere with the login process.
8961      */
8962     if (loggedOn) {
8963         if (!strncmp(message, "tellics ", 8)) {
8964             SendToICS(message + 8);
8965             SendToICS("\n");
8966             return;
8967         }
8968         if (!strncmp(message, "tellicsnoalias ", 15)) {
8969             SendToICS(ics_prefix);
8970             SendToICS(message + 15);
8971             SendToICS("\n");
8972             return;
8973         }
8974         /* The following are for backward compatibility only */
8975         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8976             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8977             SendToICS(ics_prefix);
8978             SendToICS(message);
8979             SendToICS("\n");
8980             return;
8981         }
8982     }
8983     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8984         if(initPing == cps->lastPong) {
8985             if(gameInfo.variant == VariantUnknown) {
8986                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8987                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8988                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8989             }
8990             initPing = -1;
8991         }
8992         return;
8993     }
8994     if(!strncmp(message, "highlight ", 10)) {
8995         if(appData.testLegality && appData.markers) return;
8996         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8997         return;
8998     }
8999     if(!strncmp(message, "click ", 6)) {
9000         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9001         if(appData.testLegality || !appData.oneClick) return;
9002         sscanf(message+6, "%c%d%c", &f, &y, &c);
9003         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9004         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9005         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9006         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9007         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9008         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9009             LeftClick(Release, lastLeftX, lastLeftY);
9010         controlKey  = (c == ',');
9011         LeftClick(Press, x, y);
9012         LeftClick(Release, x, y);
9013         first.highlight = f;
9014         return;
9015     }
9016     /*
9017      * If the move is illegal, cancel it and redraw the board.
9018      * Also deal with other error cases.  Matching is rather loose
9019      * here to accommodate engines written before the spec.
9020      */
9021     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9022         strncmp(message, "Error", 5) == 0) {
9023         if (StrStr(message, "name") ||
9024             StrStr(message, "rating") || StrStr(message, "?") ||
9025             StrStr(message, "result") || StrStr(message, "board") ||
9026             StrStr(message, "bk") || StrStr(message, "computer") ||
9027             StrStr(message, "variant") || StrStr(message, "hint") ||
9028             StrStr(message, "random") || StrStr(message, "depth") ||
9029             StrStr(message, "accepted")) {
9030             return;
9031         }
9032         if (StrStr(message, "protover")) {
9033           /* Program is responding to input, so it's apparently done
9034              initializing, and this error message indicates it is
9035              protocol version 1.  So we don't need to wait any longer
9036              for it to initialize and send feature commands. */
9037           FeatureDone(cps, 1);
9038           cps->protocolVersion = 1;
9039           return;
9040         }
9041         cps->maybeThinking = FALSE;
9042
9043         if (StrStr(message, "draw")) {
9044             /* Program doesn't have "draw" command */
9045             cps->sendDrawOffers = 0;
9046             return;
9047         }
9048         if (cps->sendTime != 1 &&
9049             (StrStr(message, "time") || StrStr(message, "otim"))) {
9050           /* Program apparently doesn't have "time" or "otim" command */
9051           cps->sendTime = 0;
9052           return;
9053         }
9054         if (StrStr(message, "analyze")) {
9055             cps->analysisSupport = FALSE;
9056             cps->analyzing = FALSE;
9057 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9058             EditGameEvent(); // [HGM] try to preserve loaded game
9059             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9060             DisplayError(buf2, 0);
9061             return;
9062         }
9063         if (StrStr(message, "(no matching move)st")) {
9064           /* Special kludge for GNU Chess 4 only */
9065           cps->stKludge = TRUE;
9066           SendTimeControl(cps, movesPerSession, timeControl,
9067                           timeIncrement, appData.searchDepth,
9068                           searchTime);
9069           return;
9070         }
9071         if (StrStr(message, "(no matching move)sd")) {
9072           /* Special kludge for GNU Chess 4 only */
9073           cps->sdKludge = TRUE;
9074           SendTimeControl(cps, movesPerSession, timeControl,
9075                           timeIncrement, appData.searchDepth,
9076                           searchTime);
9077           return;
9078         }
9079         if (!StrStr(message, "llegal")) {
9080             return;
9081         }
9082         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9083             gameMode == IcsIdle) return;
9084         if (forwardMostMove <= backwardMostMove) return;
9085         if (pausing) PauseEvent();
9086       if(appData.forceIllegal) {
9087             // [HGM] illegal: machine refused move; force position after move into it
9088           SendToProgram("force\n", cps);
9089           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9090                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9091                 // when black is to move, while there might be nothing on a2 or black
9092                 // might already have the move. So send the board as if white has the move.
9093                 // But first we must change the stm of the engine, as it refused the last move
9094                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9095                 if(WhiteOnMove(forwardMostMove)) {
9096                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9097                     SendBoard(cps, forwardMostMove); // kludgeless board
9098                 } else {
9099                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9100                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9101                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9102                 }
9103           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9104             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9105                  gameMode == TwoMachinesPlay)
9106               SendToProgram("go\n", cps);
9107             return;
9108       } else
9109         if (gameMode == PlayFromGameFile) {
9110             /* Stop reading this game file */
9111             gameMode = EditGame;
9112             ModeHighlight();
9113         }
9114         /* [HGM] illegal-move claim should forfeit game when Xboard */
9115         /* only passes fully legal moves                            */
9116         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9117             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9118                                 "False illegal-move claim", GE_XBOARD );
9119             return; // do not take back move we tested as valid
9120         }
9121         currentMove = forwardMostMove-1;
9122         DisplayMove(currentMove-1); /* before DisplayMoveError */
9123         SwitchClocks(forwardMostMove-1); // [HGM] race
9124         DisplayBothClocks();
9125         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9126                 parseList[currentMove], _(cps->which));
9127         DisplayMoveError(buf1);
9128         DrawPosition(FALSE, boards[currentMove]);
9129
9130         SetUserThinkingEnables();
9131         return;
9132     }
9133     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9134         /* Program has a broken "time" command that
9135            outputs a string not ending in newline.
9136            Don't use it. */
9137         cps->sendTime = 0;
9138     }
9139
9140     /*
9141      * If chess program startup fails, exit with an error message.
9142      * Attempts to recover here are futile. [HGM] Well, we try anyway
9143      */
9144     if ((StrStr(message, "unknown host") != NULL)
9145         || (StrStr(message, "No remote directory") != NULL)
9146         || (StrStr(message, "not found") != NULL)
9147         || (StrStr(message, "No such file") != NULL)
9148         || (StrStr(message, "can't alloc") != NULL)
9149         || (StrStr(message, "Permission denied") != NULL)) {
9150
9151         cps->maybeThinking = FALSE;
9152         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9153                 _(cps->which), cps->program, cps->host, message);
9154         RemoveInputSource(cps->isr);
9155         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9156             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9157             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9158         }
9159         return;
9160     }
9161
9162     /*
9163      * Look for hint output
9164      */
9165     if (sscanf(message, "Hint: %s", buf1) == 1) {
9166         if (cps == &first && hintRequested) {
9167             hintRequested = FALSE;
9168             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9169                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9170                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9171                                     PosFlags(forwardMostMove),
9172                                     fromY, fromX, toY, toX, promoChar, buf1);
9173                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9174                 DisplayInformation(buf2);
9175             } else {
9176                 /* Hint move could not be parsed!? */
9177               snprintf(buf2, sizeof(buf2),
9178                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9179                         buf1, _(cps->which));
9180                 DisplayError(buf2, 0);
9181             }
9182         } else {
9183           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9184         }
9185         return;
9186     }
9187
9188     /*
9189      * Ignore other messages if game is not in progress
9190      */
9191     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9192         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9193
9194     /*
9195      * look for win, lose, draw, or draw offer
9196      */
9197     if (strncmp(message, "1-0", 3) == 0) {
9198         char *p, *q, *r = "";
9199         p = strchr(message, '{');
9200         if (p) {
9201             q = strchr(p, '}');
9202             if (q) {
9203                 *q = NULLCHAR;
9204                 r = p + 1;
9205             }
9206         }
9207         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9208         return;
9209     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
9220         if (strcmp(r, "Black resigns") == 0) {
9221             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9222             return;
9223         }
9224         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9225         return;
9226     } else if (strncmp(message, "1/2", 3) == 0) {
9227         char *p, *q, *r = "";
9228         p = strchr(message, '{');
9229         if (p) {
9230             q = strchr(p, '}');
9231             if (q) {
9232                 *q = NULLCHAR;
9233                 r = p + 1;
9234             }
9235         }
9236
9237         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9238         return;
9239
9240     } else if (strncmp(message, "White resign", 12) == 0) {
9241         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9242         return;
9243     } else if (strncmp(message, "Black resign", 12) == 0) {
9244         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9245         return;
9246     } else if (strncmp(message, "White matches", 13) == 0 ||
9247                strncmp(message, "Black matches", 13) == 0   ) {
9248         /* [HGM] ignore GNUShogi noises */
9249         return;
9250     } else if (strncmp(message, "White", 5) == 0 &&
9251                message[5] != '(' &&
9252                StrStr(message, "Black") == NULL) {
9253         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9254         return;
9255     } else if (strncmp(message, "Black", 5) == 0 &&
9256                message[5] != '(') {
9257         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9258         return;
9259     } else if (strcmp(message, "resign") == 0 ||
9260                strcmp(message, "computer resigns") == 0) {
9261         switch (gameMode) {
9262           case MachinePlaysBlack:
9263           case IcsPlayingBlack:
9264             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9265             break;
9266           case MachinePlaysWhite:
9267           case IcsPlayingWhite:
9268             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9269             break;
9270           case TwoMachinesPlay:
9271             if (cps->twoMachinesColor[0] == 'w')
9272               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9273             else
9274               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9275             break;
9276           default:
9277             /* can't happen */
9278             break;
9279         }
9280         return;
9281     } else if (strncmp(message, "opponent mates", 14) == 0) {
9282         switch (gameMode) {
9283           case MachinePlaysBlack:
9284           case IcsPlayingBlack:
9285             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9286             break;
9287           case MachinePlaysWhite:
9288           case IcsPlayingWhite:
9289             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9290             break;
9291           case TwoMachinesPlay:
9292             if (cps->twoMachinesColor[0] == 'w')
9293               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9294             else
9295               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9296             break;
9297           default:
9298             /* can't happen */
9299             break;
9300         }
9301         return;
9302     } else if (strncmp(message, "computer mates", 14) == 0) {
9303         switch (gameMode) {
9304           case MachinePlaysBlack:
9305           case IcsPlayingBlack:
9306             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9307             break;
9308           case MachinePlaysWhite:
9309           case IcsPlayingWhite:
9310             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9311             break;
9312           case TwoMachinesPlay:
9313             if (cps->twoMachinesColor[0] == 'w')
9314               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9315             else
9316               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9317             break;
9318           default:
9319             /* can't happen */
9320             break;
9321         }
9322         return;
9323     } else if (strncmp(message, "checkmate", 9) == 0) {
9324         if (WhiteOnMove(forwardMostMove)) {
9325             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9326         } else {
9327             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9328         }
9329         return;
9330     } else if (strstr(message, "Draw") != NULL ||
9331                strstr(message, "game is a draw") != NULL) {
9332         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9333         return;
9334     } else if (strstr(message, "offer") != NULL &&
9335                strstr(message, "draw") != NULL) {
9336 #if ZIPPY
9337         if (appData.zippyPlay && first.initDone) {
9338             /* Relay offer to ICS */
9339             SendToICS(ics_prefix);
9340             SendToICS("draw\n");
9341         }
9342 #endif
9343         cps->offeredDraw = 2; /* valid until this engine moves twice */
9344         if (gameMode == TwoMachinesPlay) {
9345             if (cps->other->offeredDraw) {
9346                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9347             /* [HGM] in two-machine mode we delay relaying draw offer      */
9348             /* until after we also have move, to see if it is really claim */
9349             }
9350         } else if (gameMode == MachinePlaysWhite ||
9351                    gameMode == MachinePlaysBlack) {
9352           if (userOfferedDraw) {
9353             DisplayInformation(_("Machine accepts your draw offer"));
9354             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9355           } else {
9356             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9357           }
9358         }
9359     }
9360
9361
9362     /*
9363      * Look for thinking output
9364      */
9365     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9366           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9367                                 ) {
9368         int plylev, mvleft, mvtot, curscore, time;
9369         char mvname[MOVE_LEN];
9370         u64 nodes; // [DM]
9371         char plyext;
9372         int ignore = FALSE;
9373         int prefixHint = FALSE;
9374         mvname[0] = NULLCHAR;
9375
9376         switch (gameMode) {
9377           case MachinePlaysBlack:
9378           case IcsPlayingBlack:
9379             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9380             break;
9381           case MachinePlaysWhite:
9382           case IcsPlayingWhite:
9383             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9384             break;
9385           case AnalyzeMode:
9386           case AnalyzeFile:
9387             break;
9388           case IcsObserving: /* [DM] icsEngineAnalyze */
9389             if (!appData.icsEngineAnalyze) ignore = TRUE;
9390             break;
9391           case TwoMachinesPlay:
9392             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9393                 ignore = TRUE;
9394             }
9395             break;
9396           default:
9397             ignore = TRUE;
9398             break;
9399         }
9400
9401         if (!ignore) {
9402             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9403             buf1[0] = NULLCHAR;
9404             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9405                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9406
9407                 if (plyext != ' ' && plyext != '\t') {
9408                     time *= 100;
9409                 }
9410
9411                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9412                 if( cps->scoreIsAbsolute &&
9413                     ( gameMode == MachinePlaysBlack ||
9414                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9415                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9416                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9417                      !WhiteOnMove(currentMove)
9418                     ) )
9419                 {
9420                     curscore = -curscore;
9421                 }
9422
9423                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9424
9425                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9426                         char buf[MSG_SIZ];
9427                         FILE *f;
9428                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9429                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9430                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9431                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9432                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9433                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9434                                 fclose(f);
9435                         }
9436                         else
9437                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9438                           DisplayError(_("failed writing PV"), 0);
9439                 }
9440
9441                 tempStats.depth = plylev;
9442                 tempStats.nodes = nodes;
9443                 tempStats.time = time;
9444                 tempStats.score = curscore;
9445                 tempStats.got_only_move = 0;
9446
9447                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9448                         int ticklen;
9449
9450                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9451                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9452                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9453                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9454                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9455                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9456                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9457                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9458                 }
9459
9460                 /* Buffer overflow protection */
9461                 if (pv[0] != NULLCHAR) {
9462                     if (strlen(pv) >= sizeof(tempStats.movelist)
9463                         && appData.debugMode) {
9464                         fprintf(debugFP,
9465                                 "PV is too long; using the first %u bytes.\n",
9466                                 (unsigned) sizeof(tempStats.movelist) - 1);
9467                     }
9468
9469                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9470                 } else {
9471                     sprintf(tempStats.movelist, " no PV\n");
9472                 }
9473
9474                 if (tempStats.seen_stat) {
9475                     tempStats.ok_to_send = 1;
9476                 }
9477
9478                 if (strchr(tempStats.movelist, '(') != NULL) {
9479                     tempStats.line_is_book = 1;
9480                     tempStats.nr_moves = 0;
9481                     tempStats.moves_left = 0;
9482                 } else {
9483                     tempStats.line_is_book = 0;
9484                 }
9485
9486                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9487                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9488
9489                 SendProgramStatsToFrontend( cps, &tempStats );
9490
9491                 /*
9492                     [AS] Protect the thinkOutput buffer from overflow... this
9493                     is only useful if buf1 hasn't overflowed first!
9494                 */
9495                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9496                          plylev,
9497                          (gameMode == TwoMachinesPlay ?
9498                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9499                          ((double) curscore) / 100.0,
9500                          prefixHint ? lastHint : "",
9501                          prefixHint ? " " : "" );
9502
9503                 if( buf1[0] != NULLCHAR ) {
9504                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9505
9506                     if( strlen(pv) > max_len ) {
9507                         if( appData.debugMode) {
9508                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9509                         }
9510                         pv[max_len+1] = '\0';
9511                     }
9512
9513                     strcat( thinkOutput, pv);
9514                 }
9515
9516                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9517                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9518                     DisplayMove(currentMove - 1);
9519                 }
9520                 return;
9521
9522             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9523                 /* crafty (9.25+) says "(only move) <move>"
9524                  * if there is only 1 legal move
9525                  */
9526                 sscanf(p, "(only move) %s", buf1);
9527                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9528                 sprintf(programStats.movelist, "%s (only move)", buf1);
9529                 programStats.depth = 1;
9530                 programStats.nr_moves = 1;
9531                 programStats.moves_left = 1;
9532                 programStats.nodes = 1;
9533                 programStats.time = 1;
9534                 programStats.got_only_move = 1;
9535
9536                 /* Not really, but we also use this member to
9537                    mean "line isn't going to change" (Crafty
9538                    isn't searching, so stats won't change) */
9539                 programStats.line_is_book = 1;
9540
9541                 SendProgramStatsToFrontend( cps, &programStats );
9542
9543                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9544                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9545                     DisplayMove(currentMove - 1);
9546                 }
9547                 return;
9548             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9549                               &time, &nodes, &plylev, &mvleft,
9550                               &mvtot, mvname) >= 5) {
9551                 /* The stat01: line is from Crafty (9.29+) in response
9552                    to the "." command */
9553                 programStats.seen_stat = 1;
9554                 cps->maybeThinking = TRUE;
9555
9556                 if (programStats.got_only_move || !appData.periodicUpdates)
9557                   return;
9558
9559                 programStats.depth = plylev;
9560                 programStats.time = time;
9561                 programStats.nodes = nodes;
9562                 programStats.moves_left = mvleft;
9563                 programStats.nr_moves = mvtot;
9564                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9565                 programStats.ok_to_send = 1;
9566                 programStats.movelist[0] = '\0';
9567
9568                 SendProgramStatsToFrontend( cps, &programStats );
9569
9570                 return;
9571
9572             } else if (strncmp(message,"++",2) == 0) {
9573                 /* Crafty 9.29+ outputs this */
9574                 programStats.got_fail = 2;
9575                 return;
9576
9577             } else if (strncmp(message,"--",2) == 0) {
9578                 /* Crafty 9.29+ outputs this */
9579                 programStats.got_fail = 1;
9580                 return;
9581
9582             } else if (thinkOutput[0] != NULLCHAR &&
9583                        strncmp(message, "    ", 4) == 0) {
9584                 unsigned message_len;
9585
9586                 p = message;
9587                 while (*p && *p == ' ') p++;
9588
9589                 message_len = strlen( p );
9590
9591                 /* [AS] Avoid buffer overflow */
9592                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9593                     strcat(thinkOutput, " ");
9594                     strcat(thinkOutput, p);
9595                 }
9596
9597                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9598                     strcat(programStats.movelist, " ");
9599                     strcat(programStats.movelist, p);
9600                 }
9601
9602                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9603                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9604                     DisplayMove(currentMove - 1);
9605                 }
9606                 return;
9607             }
9608         }
9609         else {
9610             buf1[0] = NULLCHAR;
9611
9612             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9613                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9614             {
9615                 ChessProgramStats cpstats;
9616
9617                 if (plyext != ' ' && plyext != '\t') {
9618                     time *= 100;
9619                 }
9620
9621                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9622                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9623                     curscore = -curscore;
9624                 }
9625
9626                 cpstats.depth = plylev;
9627                 cpstats.nodes = nodes;
9628                 cpstats.time = time;
9629                 cpstats.score = curscore;
9630                 cpstats.got_only_move = 0;
9631                 cpstats.movelist[0] = '\0';
9632
9633                 if (buf1[0] != NULLCHAR) {
9634                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9635                 }
9636
9637                 cpstats.ok_to_send = 0;
9638                 cpstats.line_is_book = 0;
9639                 cpstats.nr_moves = 0;
9640                 cpstats.moves_left = 0;
9641
9642                 SendProgramStatsToFrontend( cps, &cpstats );
9643             }
9644         }
9645     }
9646 }
9647
9648
9649 /* Parse a game score from the character string "game", and
9650    record it as the history of the current game.  The game
9651    score is NOT assumed to start from the standard position.
9652    The display is not updated in any way.
9653    */
9654 void
9655 ParseGameHistory (char *game)
9656 {
9657     ChessMove moveType;
9658     int fromX, fromY, toX, toY, boardIndex;
9659     char promoChar;
9660     char *p, *q;
9661     char buf[MSG_SIZ];
9662
9663     if (appData.debugMode)
9664       fprintf(debugFP, "Parsing game history: %s\n", game);
9665
9666     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9667     gameInfo.site = StrSave(appData.icsHost);
9668     gameInfo.date = PGNDate();
9669     gameInfo.round = StrSave("-");
9670
9671     /* Parse out names of players */
9672     while (*game == ' ') game++;
9673     p = buf;
9674     while (*game != ' ') *p++ = *game++;
9675     *p = NULLCHAR;
9676     gameInfo.white = StrSave(buf);
9677     while (*game == ' ') game++;
9678     p = buf;
9679     while (*game != ' ' && *game != '\n') *p++ = *game++;
9680     *p = NULLCHAR;
9681     gameInfo.black = StrSave(buf);
9682
9683     /* Parse moves */
9684     boardIndex = blackPlaysFirst ? 1 : 0;
9685     yynewstr(game);
9686     for (;;) {
9687         yyboardindex = boardIndex;
9688         moveType = (ChessMove) Myylex();
9689         switch (moveType) {
9690           case IllegalMove:             /* maybe suicide chess, etc. */
9691   if (appData.debugMode) {
9692     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9693     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9694     setbuf(debugFP, NULL);
9695   }
9696           case WhitePromotion:
9697           case BlackPromotion:
9698           case WhiteNonPromotion:
9699           case BlackNonPromotion:
9700           case NormalMove:
9701           case FirstLeg:
9702           case WhiteCapturesEnPassant:
9703           case BlackCapturesEnPassant:
9704           case WhiteKingSideCastle:
9705           case WhiteQueenSideCastle:
9706           case BlackKingSideCastle:
9707           case BlackQueenSideCastle:
9708           case WhiteKingSideCastleWild:
9709           case WhiteQueenSideCastleWild:
9710           case BlackKingSideCastleWild:
9711           case BlackQueenSideCastleWild:
9712           /* PUSH Fabien */
9713           case WhiteHSideCastleFR:
9714           case WhiteASideCastleFR:
9715           case BlackHSideCastleFR:
9716           case BlackASideCastleFR:
9717           /* POP Fabien */
9718             fromX = currentMoveString[0] - AAA;
9719             fromY = currentMoveString[1] - ONE;
9720             toX = currentMoveString[2] - AAA;
9721             toY = currentMoveString[3] - ONE;
9722             promoChar = currentMoveString[4];
9723             break;
9724           case WhiteDrop:
9725           case BlackDrop:
9726             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9727             fromX = moveType == WhiteDrop ?
9728               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9729             (int) CharToPiece(ToLower(currentMoveString[0]));
9730             fromY = DROP_RANK;
9731             toX = currentMoveString[2] - AAA;
9732             toY = currentMoveString[3] - ONE;
9733             promoChar = NULLCHAR;
9734             break;
9735           case AmbiguousMove:
9736             /* bug? */
9737             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9738   if (appData.debugMode) {
9739     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9740     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9741     setbuf(debugFP, NULL);
9742   }
9743             DisplayError(buf, 0);
9744             return;
9745           case ImpossibleMove:
9746             /* bug? */
9747             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9748   if (appData.debugMode) {
9749     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9750     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9751     setbuf(debugFP, NULL);
9752   }
9753             DisplayError(buf, 0);
9754             return;
9755           case EndOfFile:
9756             if (boardIndex < backwardMostMove) {
9757                 /* Oops, gap.  How did that happen? */
9758                 DisplayError(_("Gap in move list"), 0);
9759                 return;
9760             }
9761             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9762             if (boardIndex > forwardMostMove) {
9763                 forwardMostMove = boardIndex;
9764             }
9765             return;
9766           case ElapsedTime:
9767             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9768                 strcat(parseList[boardIndex-1], " ");
9769                 strcat(parseList[boardIndex-1], yy_text);
9770             }
9771             continue;
9772           case Comment:
9773           case PGNTag:
9774           case NAG:
9775           default:
9776             /* ignore */
9777             continue;
9778           case WhiteWins:
9779           case BlackWins:
9780           case GameIsDrawn:
9781           case GameUnfinished:
9782             if (gameMode == IcsExamining) {
9783                 if (boardIndex < backwardMostMove) {
9784                     /* Oops, gap.  How did that happen? */
9785                     return;
9786                 }
9787                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9788                 return;
9789             }
9790             gameInfo.result = moveType;
9791             p = strchr(yy_text, '{');
9792             if (p == NULL) p = strchr(yy_text, '(');
9793             if (p == NULL) {
9794                 p = yy_text;
9795                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9796             } else {
9797                 q = strchr(p, *p == '{' ? '}' : ')');
9798                 if (q != NULL) *q = NULLCHAR;
9799                 p++;
9800             }
9801             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9802             gameInfo.resultDetails = StrSave(p);
9803             continue;
9804         }
9805         if (boardIndex >= forwardMostMove &&
9806             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9807             backwardMostMove = blackPlaysFirst ? 1 : 0;
9808             return;
9809         }
9810         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9811                                  fromY, fromX, toY, toX, promoChar,
9812                                  parseList[boardIndex]);
9813         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9814         /* currentMoveString is set as a side-effect of yylex */
9815         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9816         strcat(moveList[boardIndex], "\n");
9817         boardIndex++;
9818         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9819         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9820           case MT_NONE:
9821           case MT_STALEMATE:
9822           default:
9823             break;
9824           case MT_CHECK:
9825             if(!IS_SHOGI(gameInfo.variant))
9826                 strcat(parseList[boardIndex - 1], "+");
9827             break;
9828           case MT_CHECKMATE:
9829           case MT_STAINMATE:
9830             strcat(parseList[boardIndex - 1], "#");
9831             break;
9832         }
9833     }
9834 }
9835
9836
9837 /* Apply a move to the given board  */
9838 void
9839 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9840 {
9841   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9842   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9843
9844     /* [HGM] compute & store e.p. status and castling rights for new position */
9845     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9846
9847       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9848       oldEP = (signed char)board[EP_STATUS];
9849       board[EP_STATUS] = EP_NONE;
9850
9851   if (fromY == DROP_RANK) {
9852         /* must be first */
9853         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9854             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9855             return;
9856         }
9857         piece = board[toY][toX] = (ChessSquare) fromX;
9858   } else {
9859 //      ChessSquare victim;
9860       int i;
9861
9862       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9863 //           victim = board[killY][killX],
9864            board[killY][killX] = EmptySquare,
9865            board[EP_STATUS] = EP_CAPTURE;
9866
9867       if( board[toY][toX] != EmptySquare ) {
9868            board[EP_STATUS] = EP_CAPTURE;
9869            if( (fromX != toX || fromY != toY) && // not igui!
9870                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9871                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9872                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9873            }
9874       }
9875
9876       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9877            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9878                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9879       } else
9880       if( board[fromY][fromX] == WhitePawn ) {
9881            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9882                board[EP_STATUS] = EP_PAWN_MOVE;
9883            if( toY-fromY==2) {
9884                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9885                         gameInfo.variant != VariantBerolina || toX < fromX)
9886                       board[EP_STATUS] = toX | berolina;
9887                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9888                         gameInfo.variant != VariantBerolina || toX > fromX)
9889                       board[EP_STATUS] = toX;
9890            }
9891       } else
9892       if( board[fromY][fromX] == BlackPawn ) {
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] == WhitePawn &&
9897                         gameInfo.variant != VariantBerolina || toX < fromX)
9898                       board[EP_STATUS] = toX | berolina;
9899                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9900                         gameInfo.variant != VariantBerolina || toX > fromX)
9901                       board[EP_STATUS] = toX;
9902            }
9903        }
9904
9905        for(i=0; i<nrCastlingRights; i++) {
9906            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9907               board[CASTLING][i] == toX   && castlingRank[i] == toY
9908              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9909        }
9910
9911        if(gameInfo.variant == VariantSChess) { // update virginity
9912            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9913            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9914            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9915            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9916        }
9917
9918      if (fromX == toX && fromY == toY) return;
9919
9920      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9921      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9922      if(gameInfo.variant == VariantKnightmate)
9923          king += (int) WhiteUnicorn - (int) WhiteKing;
9924
9925     /* Code added by Tord: */
9926     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9927     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9928         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9929       board[fromY][fromX] = EmptySquare;
9930       board[toY][toX] = EmptySquare;
9931       if((toX > fromX) != (piece == WhiteRook)) {
9932         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9933       } else {
9934         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9935       }
9936     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9937                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9938       board[fromY][fromX] = EmptySquare;
9939       board[toY][toX] = EmptySquare;
9940       if((toX > fromX) != (piece == BlackRook)) {
9941         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9942       } else {
9943         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9944       }
9945     /* End of code added by Tord */
9946
9947     } else if (board[fromY][fromX] == king
9948         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9949         && toY == fromY && toX > fromX+1) {
9950         board[fromY][fromX] = EmptySquare;
9951         board[toY][toX] = king;
9952         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9953         board[fromY][BOARD_RGHT-1] = EmptySquare;
9954     } else if (board[fromY][fromX] == king
9955         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9956                && toY == fromY && toX < fromX-1) {
9957         board[fromY][fromX] = EmptySquare;
9958         board[toY][toX] = king;
9959         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9960         board[fromY][BOARD_LEFT] = EmptySquare;
9961     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9962                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9963                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9964                ) {
9965         /* white pawn promotion */
9966         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9967         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9968             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9969         board[fromY][fromX] = EmptySquare;
9970     } else if ((fromY >= BOARD_HEIGHT>>1)
9971                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9972                && (toX != fromX)
9973                && gameInfo.variant != VariantXiangqi
9974                && gameInfo.variant != VariantBerolina
9975                && (board[fromY][fromX] == WhitePawn)
9976                && (board[toY][toX] == EmptySquare)) {
9977         board[fromY][fromX] = EmptySquare;
9978         board[toY][toX] = WhitePawn;
9979         captured = board[toY - 1][toX];
9980         board[toY - 1][toX] = EmptySquare;
9981     } else if ((fromY == BOARD_HEIGHT-4)
9982                && (toX == fromX)
9983                && gameInfo.variant == VariantBerolina
9984                && (board[fromY][fromX] == WhitePawn)
9985                && (board[toY][toX] == EmptySquare)) {
9986         board[fromY][fromX] = EmptySquare;
9987         board[toY][toX] = WhitePawn;
9988         if(oldEP & EP_BEROLIN_A) {
9989                 captured = board[fromY][fromX-1];
9990                 board[fromY][fromX-1] = EmptySquare;
9991         }else{  captured = board[fromY][fromX+1];
9992                 board[fromY][fromX+1] = EmptySquare;
9993         }
9994     } else if (board[fromY][fromX] == king
9995         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9996                && toY == fromY && toX > fromX+1) {
9997         board[fromY][fromX] = EmptySquare;
9998         board[toY][toX] = king;
9999         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
10000         board[fromY][BOARD_RGHT-1] = EmptySquare;
10001     } else if (board[fromY][fromX] == king
10002         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10003                && toY == fromY && toX < fromX-1) {
10004         board[fromY][fromX] = EmptySquare;
10005         board[toY][toX] = king;
10006         board[toY][toX+1] = board[fromY][BOARD_LEFT];
10007         board[fromY][BOARD_LEFT] = EmptySquare;
10008     } else if (fromY == 7 && fromX == 3
10009                && board[fromY][fromX] == BlackKing
10010                && toY == 7 && toX == 5) {
10011         board[fromY][fromX] = EmptySquare;
10012         board[toY][toX] = BlackKing;
10013         board[fromY][7] = EmptySquare;
10014         board[toY][4] = BlackRook;
10015     } else if (fromY == 7 && fromX == 3
10016                && board[fromY][fromX] == BlackKing
10017                && toY == 7 && toX == 1) {
10018         board[fromY][fromX] = EmptySquare;
10019         board[toY][toX] = BlackKing;
10020         board[fromY][0] = EmptySquare;
10021         board[toY][2] = BlackRook;
10022     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10023                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10024                && toY < promoRank && promoChar
10025                ) {
10026         /* black pawn promotion */
10027         board[toY][toX] = CharToPiece(ToLower(promoChar));
10028         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10029             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10030         board[fromY][fromX] = EmptySquare;
10031     } else if ((fromY < BOARD_HEIGHT>>1)
10032                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10033                && (toX != fromX)
10034                && gameInfo.variant != VariantXiangqi
10035                && gameInfo.variant != VariantBerolina
10036                && (board[fromY][fromX] == BlackPawn)
10037                && (board[toY][toX] == EmptySquare)) {
10038         board[fromY][fromX] = EmptySquare;
10039         board[toY][toX] = BlackPawn;
10040         captured = board[toY + 1][toX];
10041         board[toY + 1][toX] = EmptySquare;
10042     } else if ((fromY == 3)
10043                && (toX == fromX)
10044                && gameInfo.variant == VariantBerolina
10045                && (board[fromY][fromX] == BlackPawn)
10046                && (board[toY][toX] == EmptySquare)) {
10047         board[fromY][fromX] = EmptySquare;
10048         board[toY][toX] = BlackPawn;
10049         if(oldEP & EP_BEROLIN_A) {
10050                 captured = board[fromY][fromX-1];
10051                 board[fromY][fromX-1] = EmptySquare;
10052         }else{  captured = board[fromY][fromX+1];
10053                 board[fromY][fromX+1] = EmptySquare;
10054         }
10055     } else {
10056         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10057         board[fromY][fromX] = EmptySquare;
10058         board[toY][toX] = piece;
10059     }
10060   }
10061
10062     if (gameInfo.holdingsWidth != 0) {
10063
10064       /* !!A lot more code needs to be written to support holdings  */
10065       /* [HGM] OK, so I have written it. Holdings are stored in the */
10066       /* penultimate board files, so they are automaticlly stored   */
10067       /* in the game history.                                       */
10068       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10069                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10070         /* Delete from holdings, by decreasing count */
10071         /* and erasing image if necessary            */
10072         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10073         if(p < (int) BlackPawn) { /* white drop */
10074              p -= (int)WhitePawn;
10075                  p = PieceToNumber((ChessSquare)p);
10076              if(p >= gameInfo.holdingsSize) p = 0;
10077              if(--board[p][BOARD_WIDTH-2] <= 0)
10078                   board[p][BOARD_WIDTH-1] = EmptySquare;
10079              if((int)board[p][BOARD_WIDTH-2] < 0)
10080                         board[p][BOARD_WIDTH-2] = 0;
10081         } else {                  /* black drop */
10082              p -= (int)BlackPawn;
10083                  p = PieceToNumber((ChessSquare)p);
10084              if(p >= gameInfo.holdingsSize) p = 0;
10085              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10086                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10087              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10088                         board[BOARD_HEIGHT-1-p][1] = 0;
10089         }
10090       }
10091       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10092           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10093         /* [HGM] holdings: Add to holdings, if holdings exist */
10094         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10095                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10096                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10097         }
10098         p = (int) captured;
10099         if (p >= (int) BlackPawn) {
10100           p -= (int)BlackPawn;
10101           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10102                   /* in Shogi restore piece to its original  first */
10103                   captured = (ChessSquare) (DEMOTED captured);
10104                   p = DEMOTED p;
10105           }
10106           p = PieceToNumber((ChessSquare)p);
10107           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10108           board[p][BOARD_WIDTH-2]++;
10109           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10110         } else {
10111           p -= (int)WhitePawn;
10112           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10113                   captured = (ChessSquare) (DEMOTED captured);
10114                   p = DEMOTED p;
10115           }
10116           p = PieceToNumber((ChessSquare)p);
10117           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10118           board[BOARD_HEIGHT-1-p][1]++;
10119           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10120         }
10121       }
10122     } else if (gameInfo.variant == VariantAtomic) {
10123       if (captured != EmptySquare) {
10124         int y, x;
10125         for (y = toY-1; y <= toY+1; y++) {
10126           for (x = toX-1; x <= toX+1; x++) {
10127             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10128                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10129               board[y][x] = EmptySquare;
10130             }
10131           }
10132         }
10133         board[toY][toX] = EmptySquare;
10134       }
10135     }
10136
10137     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10138         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10139     } else
10140     if(promoChar == '+') {
10141         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10142         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10143         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10144           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10145     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10146         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10147         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10148            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10149         board[toY][toX] = newPiece;
10150     }
10151     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10152                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10153         // [HGM] superchess: take promotion piece out of holdings
10154         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10155         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10156             if(!--board[k][BOARD_WIDTH-2])
10157                 board[k][BOARD_WIDTH-1] = EmptySquare;
10158         } else {
10159             if(!--board[BOARD_HEIGHT-1-k][1])
10160                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10161         }
10162     }
10163 }
10164
10165 /* Updates forwardMostMove */
10166 void
10167 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10168 {
10169     int x = toX, y = toY;
10170     char *s = parseList[forwardMostMove];
10171     ChessSquare p = boards[forwardMostMove][toY][toX];
10172 //    forwardMostMove++; // [HGM] bare: moved downstream
10173
10174     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10175     (void) CoordsToAlgebraic(boards[forwardMostMove],
10176                              PosFlags(forwardMostMove),
10177                              fromY, fromX, y, x, promoChar,
10178                              s);
10179     if(killX >= 0 && killY >= 0)
10180         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10181
10182     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10183         int timeLeft; static int lastLoadFlag=0; int king, piece;
10184         piece = boards[forwardMostMove][fromY][fromX];
10185         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10186         if(gameInfo.variant == VariantKnightmate)
10187             king += (int) WhiteUnicorn - (int) WhiteKing;
10188         if(forwardMostMove == 0) {
10189             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10190                 fprintf(serverMoves, "%s;", UserName());
10191             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10192                 fprintf(serverMoves, "%s;", second.tidy);
10193             fprintf(serverMoves, "%s;", first.tidy);
10194             if(gameMode == MachinePlaysWhite)
10195                 fprintf(serverMoves, "%s;", UserName());
10196             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10197                 fprintf(serverMoves, "%s;", second.tidy);
10198         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10199         lastLoadFlag = loadFlag;
10200         // print base move
10201         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10202         // print castling suffix
10203         if( toY == fromY && piece == king ) {
10204             if(toX-fromX > 1)
10205                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10206             if(fromX-toX >1)
10207                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10208         }
10209         // e.p. suffix
10210         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10211              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10212              boards[forwardMostMove][toY][toX] == EmptySquare
10213              && fromX != toX && fromY != toY)
10214                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10215         // promotion suffix
10216         if(promoChar != NULLCHAR) {
10217             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10218                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10219                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10220             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10221         }
10222         if(!loadFlag) {
10223                 char buf[MOVE_LEN*2], *p; int len;
10224             fprintf(serverMoves, "/%d/%d",
10225                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10226             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10227             else                      timeLeft = blackTimeRemaining/1000;
10228             fprintf(serverMoves, "/%d", timeLeft);
10229                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10230                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10231                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10232                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10233             fprintf(serverMoves, "/%s", buf);
10234         }
10235         fflush(serverMoves);
10236     }
10237
10238     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10239         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10240       return;
10241     }
10242     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10243     if (commentList[forwardMostMove+1] != NULL) {
10244         free(commentList[forwardMostMove+1]);
10245         commentList[forwardMostMove+1] = NULL;
10246     }
10247     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10248     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10249     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10250     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10251     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10252     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10253     adjustedClock = FALSE;
10254     gameInfo.result = GameUnfinished;
10255     if (gameInfo.resultDetails != NULL) {
10256         free(gameInfo.resultDetails);
10257         gameInfo.resultDetails = NULL;
10258     }
10259     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10260                               moveList[forwardMostMove - 1]);
10261     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10262       case MT_NONE:
10263       case MT_STALEMATE:
10264       default:
10265         break;
10266       case MT_CHECK:
10267         if(!IS_SHOGI(gameInfo.variant))
10268             strcat(parseList[forwardMostMove - 1], "+");
10269         break;
10270       case MT_CHECKMATE:
10271       case MT_STAINMATE:
10272         strcat(parseList[forwardMostMove - 1], "#");
10273         break;
10274     }
10275 }
10276
10277 /* Updates currentMove if not pausing */
10278 void
10279 ShowMove (int fromX, int fromY, int toX, int toY)
10280 {
10281     int instant = (gameMode == PlayFromGameFile) ?
10282         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10283     if(appData.noGUI) return;
10284     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10285         if (!instant) {
10286             if (forwardMostMove == currentMove + 1) {
10287                 AnimateMove(boards[forwardMostMove - 1],
10288                             fromX, fromY, toX, toY);
10289             }
10290         }
10291         currentMove = forwardMostMove;
10292     }
10293
10294     killX = killY = -1; // [HGM] lion: used up
10295
10296     if (instant) return;
10297
10298     DisplayMove(currentMove - 1);
10299     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10300             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10301                 SetHighlights(fromX, fromY, toX, toY);
10302             }
10303     }
10304     DrawPosition(FALSE, boards[currentMove]);
10305     DisplayBothClocks();
10306     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10307 }
10308
10309 void
10310 SendEgtPath (ChessProgramState *cps)
10311 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10312         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10313
10314         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10315
10316         while(*p) {
10317             char c, *q = name+1, *r, *s;
10318
10319             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10320             while(*p && *p != ',') *q++ = *p++;
10321             *q++ = ':'; *q = 0;
10322             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10323                 strcmp(name, ",nalimov:") == 0 ) {
10324                 // take nalimov path from the menu-changeable option first, if it is defined
10325               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10326                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10327             } else
10328             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10329                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10330                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10331                 s = r = StrStr(s, ":") + 1; // beginning of path info
10332                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10333                 c = *r; *r = 0;             // temporarily null-terminate path info
10334                     *--q = 0;               // strip of trailig ':' from name
10335                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10336                 *r = c;
10337                 SendToProgram(buf,cps);     // send egtbpath command for this format
10338             }
10339             if(*p == ',') p++; // read away comma to position for next format name
10340         }
10341 }
10342
10343 static int
10344 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10345 {
10346       int width = 8, height = 8, holdings = 0;             // most common sizes
10347       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10348       // correct the deviations default for each variant
10349       if( v == VariantXiangqi ) width = 9,  height = 10;
10350       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10351       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10352       if( v == VariantCapablanca || v == VariantCapaRandom ||
10353           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10354                                 width = 10;
10355       if( v == VariantCourier ) width = 12;
10356       if( v == VariantSuper )                            holdings = 8;
10357       if( v == VariantGreat )   width = 10,              holdings = 8;
10358       if( v == VariantSChess )                           holdings = 7;
10359       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10360       if( v == VariantChuChess) width = 10, height = 10;
10361       if( v == VariantChu )     width = 12, height = 12;
10362       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10363              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10364              holdingsSize >= 0 && holdingsSize != holdings;
10365 }
10366
10367 char variantError[MSG_SIZ];
10368
10369 char *
10370 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10371 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10372       char *p, *variant = VariantName(v);
10373       static char b[MSG_SIZ];
10374       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10375            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10376                                                holdingsSize, variant); // cook up sized variant name
10377            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10378            if(StrStr(list, b) == NULL) {
10379                // specific sized variant not known, check if general sizing allowed
10380                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10381                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10382                             boardWidth, boardHeight, holdingsSize, engine);
10383                    return NULL;
10384                }
10385                /* [HGM] here we really should compare with the maximum supported board size */
10386            }
10387       } else snprintf(b, MSG_SIZ,"%s", variant);
10388       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10389       p = StrStr(list, b);
10390       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10391       if(p == NULL) {
10392           // occurs not at all in list, or only as sub-string
10393           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10394           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10395               int l = strlen(variantError);
10396               char *q;
10397               while(p != list && p[-1] != ',') p--;
10398               q = strchr(p, ',');
10399               if(q) *q = NULLCHAR;
10400               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10401               if(q) *q= ',';
10402           }
10403           return NULL;
10404       }
10405       return b;
10406 }
10407
10408 void
10409 InitChessProgram (ChessProgramState *cps, int setup)
10410 /* setup needed to setup FRC opening position */
10411 {
10412     char buf[MSG_SIZ], *b;
10413     if (appData.noChessProgram) return;
10414     hintRequested = FALSE;
10415     bookRequested = FALSE;
10416
10417     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10418     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10419     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10420     if(cps->memSize) { /* [HGM] memory */
10421       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10422         SendToProgram(buf, cps);
10423     }
10424     SendEgtPath(cps); /* [HGM] EGT */
10425     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10426       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10427         SendToProgram(buf, cps);
10428     }
10429
10430     setboardSpoiledMachineBlack = FALSE;
10431     SendToProgram(cps->initString, cps);
10432     if (gameInfo.variant != VariantNormal &&
10433         gameInfo.variant != VariantLoadable
10434         /* [HGM] also send variant if board size non-standard */
10435         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10436
10437       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10438                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10439       if (b == NULL) {
10440         DisplayFatalError(variantError, 0, 1);
10441         return;
10442       }
10443
10444       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10445       SendToProgram(buf, cps);
10446     }
10447     currentlyInitializedVariant = gameInfo.variant;
10448
10449     /* [HGM] send opening position in FRC to first engine */
10450     if(setup) {
10451           SendToProgram("force\n", cps);
10452           SendBoard(cps, 0);
10453           /* engine is now in force mode! Set flag to wake it up after first move. */
10454           setboardSpoiledMachineBlack = 1;
10455     }
10456
10457     if (cps->sendICS) {
10458       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10459       SendToProgram(buf, cps);
10460     }
10461     cps->maybeThinking = FALSE;
10462     cps->offeredDraw = 0;
10463     if (!appData.icsActive) {
10464         SendTimeControl(cps, movesPerSession, timeControl,
10465                         timeIncrement, appData.searchDepth,
10466                         searchTime);
10467     }
10468     if (appData.showThinking
10469         // [HGM] thinking: four options require thinking output to be sent
10470         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10471                                 ) {
10472         SendToProgram("post\n", cps);
10473     }
10474     SendToProgram("hard\n", cps);
10475     if (!appData.ponderNextMove) {
10476         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10477            it without being sure what state we are in first.  "hard"
10478            is not a toggle, so that one is OK.
10479          */
10480         SendToProgram("easy\n", cps);
10481     }
10482     if (cps->usePing) {
10483       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10484       SendToProgram(buf, cps);
10485     }
10486     cps->initDone = TRUE;
10487     ClearEngineOutputPane(cps == &second);
10488 }
10489
10490
10491 void
10492 ResendOptions (ChessProgramState *cps)
10493 { // send the stored value of the options
10494   int i;
10495   char buf[MSG_SIZ];
10496   Option *opt = cps->option;
10497   for(i=0; i<cps->nrOptions; i++, opt++) {
10498       switch(opt->type) {
10499         case Spin:
10500         case Slider:
10501         case CheckBox:
10502             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10503           break;
10504         case ComboBox:
10505           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10506           break;
10507         default:
10508             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10509           break;
10510         case Button:
10511         case SaveButton:
10512           continue;
10513       }
10514       SendToProgram(buf, cps);
10515   }
10516 }
10517
10518 void
10519 StartChessProgram (ChessProgramState *cps)
10520 {
10521     char buf[MSG_SIZ];
10522     int err;
10523
10524     if (appData.noChessProgram) return;
10525     cps->initDone = FALSE;
10526
10527     if (strcmp(cps->host, "localhost") == 0) {
10528         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10529     } else if (*appData.remoteShell == NULLCHAR) {
10530         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10531     } else {
10532         if (*appData.remoteUser == NULLCHAR) {
10533           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10534                     cps->program);
10535         } else {
10536           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10537                     cps->host, appData.remoteUser, cps->program);
10538         }
10539         err = StartChildProcess(buf, "", &cps->pr);
10540     }
10541
10542     if (err != 0) {
10543       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10544         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10545         if(cps != &first) return;
10546         appData.noChessProgram = TRUE;
10547         ThawUI();
10548         SetNCPMode();
10549 //      DisplayFatalError(buf, err, 1);
10550 //      cps->pr = NoProc;
10551 //      cps->isr = NULL;
10552         return;
10553     }
10554
10555     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10556     if (cps->protocolVersion > 1) {
10557       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10558       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10559         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10560         cps->comboCnt = 0;  //                and values of combo boxes
10561       }
10562       SendToProgram(buf, cps);
10563       if(cps->reload) ResendOptions(cps);
10564     } else {
10565       SendToProgram("xboard\n", cps);
10566     }
10567 }
10568
10569 void
10570 TwoMachinesEventIfReady P((void))
10571 {
10572   static int curMess = 0;
10573   if (first.lastPing != first.lastPong) {
10574     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10575     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10576     return;
10577   }
10578   if (second.lastPing != second.lastPong) {
10579     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10580     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10581     return;
10582   }
10583   DisplayMessage("", ""); curMess = 0;
10584   TwoMachinesEvent();
10585 }
10586
10587 char *
10588 MakeName (char *template)
10589 {
10590     time_t clock;
10591     struct tm *tm;
10592     static char buf[MSG_SIZ];
10593     char *p = buf;
10594     int i;
10595
10596     clock = time((time_t *)NULL);
10597     tm = localtime(&clock);
10598
10599     while(*p++ = *template++) if(p[-1] == '%') {
10600         switch(*template++) {
10601           case 0:   *p = 0; return buf;
10602           case 'Y': i = tm->tm_year+1900; break;
10603           case 'y': i = tm->tm_year-100; break;
10604           case 'M': i = tm->tm_mon+1; break;
10605           case 'd': i = tm->tm_mday; break;
10606           case 'h': i = tm->tm_hour; break;
10607           case 'm': i = tm->tm_min; break;
10608           case 's': i = tm->tm_sec; break;
10609           default:  i = 0;
10610         }
10611         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10612     }
10613     return buf;
10614 }
10615
10616 int
10617 CountPlayers (char *p)
10618 {
10619     int n = 0;
10620     while(p = strchr(p, '\n')) p++, n++; // count participants
10621     return n;
10622 }
10623
10624 FILE *
10625 WriteTourneyFile (char *results, FILE *f)
10626 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10627     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10628     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10629         // create a file with tournament description
10630         fprintf(f, "-participants {%s}\n", appData.participants);
10631         fprintf(f, "-seedBase %d\n", appData.seedBase);
10632         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10633         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10634         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10635         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10636         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10637         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10638         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10639         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10640         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10641         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10642         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10643         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10644         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10645         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10646         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10647         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10648         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10649         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10650         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10651         fprintf(f, "-smpCores %d\n", appData.smpCores);
10652         if(searchTime > 0)
10653                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10654         else {
10655                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10656                 fprintf(f, "-tc %s\n", appData.timeControl);
10657                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10658         }
10659         fprintf(f, "-results \"%s\"\n", results);
10660     }
10661     return f;
10662 }
10663
10664 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10665
10666 void
10667 Substitute (char *participants, int expunge)
10668 {
10669     int i, changed, changes=0, nPlayers=0;
10670     char *p, *q, *r, buf[MSG_SIZ];
10671     if(participants == NULL) return;
10672     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10673     r = p = participants; q = appData.participants;
10674     while(*p && *p == *q) {
10675         if(*p == '\n') r = p+1, nPlayers++;
10676         p++; q++;
10677     }
10678     if(*p) { // difference
10679         while(*p && *p++ != '\n');
10680         while(*q && *q++ != '\n');
10681       changed = nPlayers;
10682         changes = 1 + (strcmp(p, q) != 0);
10683     }
10684     if(changes == 1) { // a single engine mnemonic was changed
10685         q = r; while(*q) nPlayers += (*q++ == '\n');
10686         p = buf; while(*r && (*p = *r++) != '\n') p++;
10687         *p = NULLCHAR;
10688         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10689         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10690         if(mnemonic[i]) { // The substitute is valid
10691             FILE *f;
10692             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10693                 flock(fileno(f), LOCK_EX);
10694                 ParseArgsFromFile(f);
10695                 fseek(f, 0, SEEK_SET);
10696                 FREE(appData.participants); appData.participants = participants;
10697                 if(expunge) { // erase results of replaced engine
10698                     int len = strlen(appData.results), w, b, dummy;
10699                     for(i=0; i<len; i++) {
10700                         Pairing(i, nPlayers, &w, &b, &dummy);
10701                         if((w == changed || b == changed) && appData.results[i] == '*') {
10702                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10703                             fclose(f);
10704                             return;
10705                         }
10706                     }
10707                     for(i=0; i<len; i++) {
10708                         Pairing(i, nPlayers, &w, &b, &dummy);
10709                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10710                     }
10711                 }
10712                 WriteTourneyFile(appData.results, f);
10713                 fclose(f); // release lock
10714                 return;
10715             }
10716         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10717     }
10718     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10719     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10720     free(participants);
10721     return;
10722 }
10723
10724 int
10725 CheckPlayers (char *participants)
10726 {
10727         int i;
10728         char buf[MSG_SIZ], *p;
10729         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10730         while(p = strchr(participants, '\n')) {
10731             *p = NULLCHAR;
10732             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10733             if(!mnemonic[i]) {
10734                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10735                 *p = '\n';
10736                 DisplayError(buf, 0);
10737                 return 1;
10738             }
10739             *p = '\n';
10740             participants = p + 1;
10741         }
10742         return 0;
10743 }
10744
10745 int
10746 CreateTourney (char *name)
10747 {
10748         FILE *f;
10749         if(matchMode && strcmp(name, appData.tourneyFile)) {
10750              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10751         }
10752         if(name[0] == NULLCHAR) {
10753             if(appData.participants[0])
10754                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10755             return 0;
10756         }
10757         f = fopen(name, "r");
10758         if(f) { // file exists
10759             ASSIGN(appData.tourneyFile, name);
10760             ParseArgsFromFile(f); // parse it
10761         } else {
10762             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10763             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10764                 DisplayError(_("Not enough participants"), 0);
10765                 return 0;
10766             }
10767             if(CheckPlayers(appData.participants)) return 0;
10768             ASSIGN(appData.tourneyFile, name);
10769             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10770             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10771         }
10772         fclose(f);
10773         appData.noChessProgram = FALSE;
10774         appData.clockMode = TRUE;
10775         SetGNUMode();
10776         return 1;
10777 }
10778
10779 int
10780 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10781 {
10782     char buf[MSG_SIZ], *p, *q;
10783     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10784     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10785     skip = !all && group[0]; // if group requested, we start in skip mode
10786     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10787         p = names; q = buf; header = 0;
10788         while(*p && *p != '\n') *q++ = *p++;
10789         *q = 0;
10790         if(*p == '\n') p++;
10791         if(buf[0] == '#') {
10792             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10793             depth++; // we must be entering a new group
10794             if(all) continue; // suppress printing group headers when complete list requested
10795             header = 1;
10796             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10797         }
10798         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10799         if(engineList[i]) free(engineList[i]);
10800         engineList[i] = strdup(buf);
10801         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10802         if(engineMnemonic[i]) free(engineMnemonic[i]);
10803         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10804             strcat(buf, " (");
10805             sscanf(q + 8, "%s", buf + strlen(buf));
10806             strcat(buf, ")");
10807         }
10808         engineMnemonic[i] = strdup(buf);
10809         i++;
10810     }
10811     engineList[i] = engineMnemonic[i] = NULL;
10812     return i;
10813 }
10814
10815 // following implemented as macro to avoid type limitations
10816 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10817
10818 void
10819 SwapEngines (int n)
10820 {   // swap settings for first engine and other engine (so far only some selected options)
10821     int h;
10822     char *p;
10823     if(n == 0) return;
10824     SWAP(directory, p)
10825     SWAP(chessProgram, p)
10826     SWAP(isUCI, h)
10827     SWAP(hasOwnBookUCI, h)
10828     SWAP(protocolVersion, h)
10829     SWAP(reuse, h)
10830     SWAP(scoreIsAbsolute, h)
10831     SWAP(timeOdds, h)
10832     SWAP(logo, p)
10833     SWAP(pgnName, p)
10834     SWAP(pvSAN, h)
10835     SWAP(engOptions, p)
10836     SWAP(engInitString, p)
10837     SWAP(computerString, p)
10838     SWAP(features, p)
10839     SWAP(fenOverride, p)
10840     SWAP(NPS, h)
10841     SWAP(accumulateTC, h)
10842     SWAP(drawDepth, h)
10843     SWAP(host, p)
10844 }
10845
10846 int
10847 GetEngineLine (char *s, int n)
10848 {
10849     int i;
10850     char buf[MSG_SIZ];
10851     extern char *icsNames;
10852     if(!s || !*s) return 0;
10853     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10854     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10855     if(!mnemonic[i]) return 0;
10856     if(n == 11) return 1; // just testing if there was a match
10857     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10858     if(n == 1) SwapEngines(n);
10859     ParseArgsFromString(buf);
10860     if(n == 1) SwapEngines(n);
10861     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10862         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10863         ParseArgsFromString(buf);
10864     }
10865     return 1;
10866 }
10867
10868 int
10869 SetPlayer (int player, char *p)
10870 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10871     int i;
10872     char buf[MSG_SIZ], *engineName;
10873     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10874     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10875     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10876     if(mnemonic[i]) {
10877         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10878         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10879         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10880         ParseArgsFromString(buf);
10881     } else { // no engine with this nickname is installed!
10882         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10883         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10884         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10885         ModeHighlight();
10886         DisplayError(buf, 0);
10887         return 0;
10888     }
10889     free(engineName);
10890     return i;
10891 }
10892
10893 char *recentEngines;
10894
10895 void
10896 RecentEngineEvent (int nr)
10897 {
10898     int n;
10899 //    SwapEngines(1); // bump first to second
10900 //    ReplaceEngine(&second, 1); // and load it there
10901     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10902     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10903     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10904         ReplaceEngine(&first, 0);
10905         FloatToFront(&appData.recentEngineList, command[n]);
10906     }
10907 }
10908
10909 int
10910 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10911 {   // determine players from game number
10912     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10913
10914     if(appData.tourneyType == 0) {
10915         roundsPerCycle = (nPlayers - 1) | 1;
10916         pairingsPerRound = nPlayers / 2;
10917     } else if(appData.tourneyType > 0) {
10918         roundsPerCycle = nPlayers - appData.tourneyType;
10919         pairingsPerRound = appData.tourneyType;
10920     }
10921     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10922     gamesPerCycle = gamesPerRound * roundsPerCycle;
10923     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10924     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10925     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10926     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10927     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10928     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10929
10930     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10931     if(appData.roundSync) *syncInterval = gamesPerRound;
10932
10933     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10934
10935     if(appData.tourneyType == 0) {
10936         if(curPairing == (nPlayers-1)/2 ) {
10937             *whitePlayer = curRound;
10938             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10939         } else {
10940             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10941             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10942             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10943             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10944         }
10945     } else if(appData.tourneyType > 1) {
10946         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10947         *whitePlayer = curRound + appData.tourneyType;
10948     } else if(appData.tourneyType > 0) {
10949         *whitePlayer = curPairing;
10950         *blackPlayer = curRound + appData.tourneyType;
10951     }
10952
10953     // take care of white/black alternation per round.
10954     // For cycles and games this is already taken care of by default, derived from matchGame!
10955     return curRound & 1;
10956 }
10957
10958 int
10959 NextTourneyGame (int nr, int *swapColors)
10960 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10961     char *p, *q;
10962     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10963     FILE *tf;
10964     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10965     tf = fopen(appData.tourneyFile, "r");
10966     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10967     ParseArgsFromFile(tf); fclose(tf);
10968     InitTimeControls(); // TC might be altered from tourney file
10969
10970     nPlayers = CountPlayers(appData.participants); // count participants
10971     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10972     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10973
10974     if(syncInterval) {
10975         p = q = appData.results;
10976         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10977         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10978             DisplayMessage(_("Waiting for other game(s)"),"");
10979             waitingForGame = TRUE;
10980             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10981             return 0;
10982         }
10983         waitingForGame = FALSE;
10984     }
10985
10986     if(appData.tourneyType < 0) {
10987         if(nr>=0 && !pairingReceived) {
10988             char buf[1<<16];
10989             if(pairing.pr == NoProc) {
10990                 if(!appData.pairingEngine[0]) {
10991                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10992                     return 0;
10993                 }
10994                 StartChessProgram(&pairing); // starts the pairing engine
10995             }
10996             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10997             SendToProgram(buf, &pairing);
10998             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10999             SendToProgram(buf, &pairing);
11000             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11001         }
11002         pairingReceived = 0;                              // ... so we continue here
11003         *swapColors = 0;
11004         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11005         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11006         matchGame = 1; roundNr = nr / syncInterval + 1;
11007     }
11008
11009     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11010
11011     // redefine engines, engine dir, etc.
11012     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11013     if(first.pr == NoProc) {
11014       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11015       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11016     }
11017     if(second.pr == NoProc) {
11018       SwapEngines(1);
11019       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11020       SwapEngines(1);         // and make that valid for second engine by swapping
11021       InitEngine(&second, 1);
11022     }
11023     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11024     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11025     return OK;
11026 }
11027
11028 void
11029 NextMatchGame ()
11030 {   // performs game initialization that does not invoke engines, and then tries to start the game
11031     int res, firstWhite, swapColors = 0;
11032     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11033     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
11034         char buf[MSG_SIZ];
11035         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11036         if(strcmp(buf, currentDebugFile)) { // name has changed
11037             FILE *f = fopen(buf, "w");
11038             if(f) { // if opening the new file failed, just keep using the old one
11039                 ASSIGN(currentDebugFile, buf);
11040                 fclose(debugFP);
11041                 debugFP = f;
11042             }
11043             if(appData.serverFileName) {
11044                 if(serverFP) fclose(serverFP);
11045                 serverFP = fopen(appData.serverFileName, "w");
11046                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11047                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11048             }
11049         }
11050     }
11051     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11052     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11053     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11054     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11055     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11056     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11057     Reset(FALSE, first.pr != NoProc);
11058     res = LoadGameOrPosition(matchGame); // setup game
11059     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11060     if(!res) return; // abort when bad game/pos file
11061     TwoMachinesEvent();
11062 }
11063
11064 void
11065 UserAdjudicationEvent (int result)
11066 {
11067     ChessMove gameResult = GameIsDrawn;
11068
11069     if( result > 0 ) {
11070         gameResult = WhiteWins;
11071     }
11072     else if( result < 0 ) {
11073         gameResult = BlackWins;
11074     }
11075
11076     if( gameMode == TwoMachinesPlay ) {
11077         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11078     }
11079 }
11080
11081
11082 // [HGM] save: calculate checksum of game to make games easily identifiable
11083 int
11084 StringCheckSum (char *s)
11085 {
11086         int i = 0;
11087         if(s==NULL) return 0;
11088         while(*s) i = i*259 + *s++;
11089         return i;
11090 }
11091
11092 int
11093 GameCheckSum ()
11094 {
11095         int i, sum=0;
11096         for(i=backwardMostMove; i<forwardMostMove; i++) {
11097                 sum += pvInfoList[i].depth;
11098                 sum += StringCheckSum(parseList[i]);
11099                 sum += StringCheckSum(commentList[i]);
11100                 sum *= 261;
11101         }
11102         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11103         return sum + StringCheckSum(commentList[i]);
11104 } // end of save patch
11105
11106 void
11107 GameEnds (ChessMove result, char *resultDetails, int whosays)
11108 {
11109     GameMode nextGameMode;
11110     int isIcsGame;
11111     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11112
11113     if(endingGame) return; /* [HGM] crash: forbid recursion */
11114     endingGame = 1;
11115     if(twoBoards) { // [HGM] dual: switch back to one board
11116         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11117         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11118     }
11119     if (appData.debugMode) {
11120       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11121               result, resultDetails ? resultDetails : "(null)", whosays);
11122     }
11123
11124     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11125
11126     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11127
11128     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11129         /* If we are playing on ICS, the server decides when the
11130            game is over, but the engine can offer to draw, claim
11131            a draw, or resign.
11132          */
11133 #if ZIPPY
11134         if (appData.zippyPlay && first.initDone) {
11135             if (result == GameIsDrawn) {
11136                 /* In case draw still needs to be claimed */
11137                 SendToICS(ics_prefix);
11138                 SendToICS("draw\n");
11139             } else if (StrCaseStr(resultDetails, "resign")) {
11140                 SendToICS(ics_prefix);
11141                 SendToICS("resign\n");
11142             }
11143         }
11144 #endif
11145         endingGame = 0; /* [HGM] crash */
11146         return;
11147     }
11148
11149     /* If we're loading the game from a file, stop */
11150     if (whosays == GE_FILE) {
11151       (void) StopLoadGameTimer();
11152       gameFileFP = NULL;
11153     }
11154
11155     /* Cancel draw offers */
11156     first.offeredDraw = second.offeredDraw = 0;
11157
11158     /* If this is an ICS game, only ICS can really say it's done;
11159        if not, anyone can. */
11160     isIcsGame = (gameMode == IcsPlayingWhite ||
11161                  gameMode == IcsPlayingBlack ||
11162                  gameMode == IcsObserving    ||
11163                  gameMode == IcsExamining);
11164
11165     if (!isIcsGame || whosays == GE_ICS) {
11166         /* OK -- not an ICS game, or ICS said it was done */
11167         StopClocks();
11168         if (!isIcsGame && !appData.noChessProgram)
11169           SetUserThinkingEnables();
11170
11171         /* [HGM] if a machine claims the game end we verify this claim */
11172         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11173             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11174                 char claimer;
11175                 ChessMove trueResult = (ChessMove) -1;
11176
11177                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11178                                             first.twoMachinesColor[0] :
11179                                             second.twoMachinesColor[0] ;
11180
11181                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11182                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11183                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11184                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11185                 } else
11186                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11187                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11188                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11189                 } else
11190                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11191                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11192                 }
11193
11194                 // now verify win claims, but not in drop games, as we don't understand those yet
11195                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11196                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11197                     (result == WhiteWins && claimer == 'w' ||
11198                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11199                       if (appData.debugMode) {
11200                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11201                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11202                       }
11203                       if(result != trueResult) {
11204                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11205                               result = claimer == 'w' ? BlackWins : WhiteWins;
11206                               resultDetails = buf;
11207                       }
11208                 } else
11209                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11210                     && (forwardMostMove <= backwardMostMove ||
11211                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11212                         (claimer=='b')==(forwardMostMove&1))
11213                                                                                   ) {
11214                       /* [HGM] verify: draws that were not flagged are false claims */
11215                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11216                       result = claimer == 'w' ? BlackWins : WhiteWins;
11217                       resultDetails = buf;
11218                 }
11219                 /* (Claiming a loss is accepted no questions asked!) */
11220             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11221                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11222                 result = GameUnfinished;
11223                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11224             }
11225             /* [HGM] bare: don't allow bare King to win */
11226             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11227                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11228                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11229                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11230                && result != GameIsDrawn)
11231             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11232                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11233                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11234                         if(p >= 0 && p <= (int)WhiteKing) k++;
11235                 }
11236                 if (appData.debugMode) {
11237                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11238                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11239                 }
11240                 if(k <= 1) {
11241                         result = GameIsDrawn;
11242                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11243                         resultDetails = buf;
11244                 }
11245             }
11246         }
11247
11248
11249         if(serverMoves != NULL && !loadFlag) { char c = '=';
11250             if(result==WhiteWins) c = '+';
11251             if(result==BlackWins) c = '-';
11252             if(resultDetails != NULL)
11253                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11254         }
11255         if (resultDetails != NULL) {
11256             gameInfo.result = result;
11257             gameInfo.resultDetails = StrSave(resultDetails);
11258
11259             /* display last move only if game was not loaded from file */
11260             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11261                 DisplayMove(currentMove - 1);
11262
11263             if (forwardMostMove != 0) {
11264                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11265                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11266                                                                 ) {
11267                     if (*appData.saveGameFile != NULLCHAR) {
11268                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11269                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11270                         else
11271                         SaveGameToFile(appData.saveGameFile, TRUE);
11272                     } else if (appData.autoSaveGames) {
11273                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11274                     }
11275                     if (*appData.savePositionFile != NULLCHAR) {
11276                         SavePositionToFile(appData.savePositionFile);
11277                     }
11278                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11279                 }
11280             }
11281
11282             /* Tell program how game ended in case it is learning */
11283             /* [HGM] Moved this to after saving the PGN, just in case */
11284             /* engine died and we got here through time loss. In that */
11285             /* case we will get a fatal error writing the pipe, which */
11286             /* would otherwise lose us the PGN.                       */
11287             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11288             /* output during GameEnds should never be fatal anymore   */
11289             if (gameMode == MachinePlaysWhite ||
11290                 gameMode == MachinePlaysBlack ||
11291                 gameMode == TwoMachinesPlay ||
11292                 gameMode == IcsPlayingWhite ||
11293                 gameMode == IcsPlayingBlack ||
11294                 gameMode == BeginningOfGame) {
11295                 char buf[MSG_SIZ];
11296                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11297                         resultDetails);
11298                 if (first.pr != NoProc) {
11299                     SendToProgram(buf, &first);
11300                 }
11301                 if (second.pr != NoProc &&
11302                     gameMode == TwoMachinesPlay) {
11303                     SendToProgram(buf, &second);
11304                 }
11305             }
11306         }
11307
11308         if (appData.icsActive) {
11309             if (appData.quietPlay &&
11310                 (gameMode == IcsPlayingWhite ||
11311                  gameMode == IcsPlayingBlack)) {
11312                 SendToICS(ics_prefix);
11313                 SendToICS("set shout 1\n");
11314             }
11315             nextGameMode = IcsIdle;
11316             ics_user_moved = FALSE;
11317             /* clean up premove.  It's ugly when the game has ended and the
11318              * premove highlights are still on the board.
11319              */
11320             if (gotPremove) {
11321               gotPremove = FALSE;
11322               ClearPremoveHighlights();
11323               DrawPosition(FALSE, boards[currentMove]);
11324             }
11325             if (whosays == GE_ICS) {
11326                 switch (result) {
11327                 case WhiteWins:
11328                     if (gameMode == IcsPlayingWhite)
11329                         PlayIcsWinSound();
11330                     else if(gameMode == IcsPlayingBlack)
11331                         PlayIcsLossSound();
11332                     break;
11333                 case BlackWins:
11334                     if (gameMode == IcsPlayingBlack)
11335                         PlayIcsWinSound();
11336                     else if(gameMode == IcsPlayingWhite)
11337                         PlayIcsLossSound();
11338                     break;
11339                 case GameIsDrawn:
11340                     PlayIcsDrawSound();
11341                     break;
11342                 default:
11343                     PlayIcsUnfinishedSound();
11344                 }
11345             }
11346             if(appData.quitNext) { ExitEvent(0); return; }
11347         } else if (gameMode == EditGame ||
11348                    gameMode == PlayFromGameFile ||
11349                    gameMode == AnalyzeMode ||
11350                    gameMode == AnalyzeFile) {
11351             nextGameMode = gameMode;
11352         } else {
11353             nextGameMode = EndOfGame;
11354         }
11355         pausing = FALSE;
11356         ModeHighlight();
11357     } else {
11358         nextGameMode = gameMode;
11359     }
11360
11361     if (appData.noChessProgram) {
11362         gameMode = nextGameMode;
11363         ModeHighlight();
11364         endingGame = 0; /* [HGM] crash */
11365         return;
11366     }
11367
11368     if (first.reuse) {
11369         /* Put first chess program into idle state */
11370         if (first.pr != NoProc &&
11371             (gameMode == MachinePlaysWhite ||
11372              gameMode == MachinePlaysBlack ||
11373              gameMode == TwoMachinesPlay ||
11374              gameMode == IcsPlayingWhite ||
11375              gameMode == IcsPlayingBlack ||
11376              gameMode == BeginningOfGame)) {
11377             SendToProgram("force\n", &first);
11378             if (first.usePing) {
11379               char buf[MSG_SIZ];
11380               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11381               SendToProgram(buf, &first);
11382             }
11383         }
11384     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11385         /* Kill off first chess program */
11386         if (first.isr != NULL)
11387           RemoveInputSource(first.isr);
11388         first.isr = NULL;
11389
11390         if (first.pr != NoProc) {
11391             ExitAnalyzeMode();
11392             DoSleep( appData.delayBeforeQuit );
11393             SendToProgram("quit\n", &first);
11394             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11395             first.reload = TRUE;
11396         }
11397         first.pr = NoProc;
11398     }
11399     if (second.reuse) {
11400         /* Put second chess program into idle state */
11401         if (second.pr != NoProc &&
11402             gameMode == TwoMachinesPlay) {
11403             SendToProgram("force\n", &second);
11404             if (second.usePing) {
11405               char buf[MSG_SIZ];
11406               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11407               SendToProgram(buf, &second);
11408             }
11409         }
11410     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11411         /* Kill off second chess program */
11412         if (second.isr != NULL)
11413           RemoveInputSource(second.isr);
11414         second.isr = NULL;
11415
11416         if (second.pr != NoProc) {
11417             DoSleep( appData.delayBeforeQuit );
11418             SendToProgram("quit\n", &second);
11419             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11420             second.reload = TRUE;
11421         }
11422         second.pr = NoProc;
11423     }
11424
11425     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11426         char resChar = '=';
11427         switch (result) {
11428         case WhiteWins:
11429           resChar = '+';
11430           if (first.twoMachinesColor[0] == 'w') {
11431             first.matchWins++;
11432           } else {
11433             second.matchWins++;
11434           }
11435           break;
11436         case BlackWins:
11437           resChar = '-';
11438           if (first.twoMachinesColor[0] == 'b') {
11439             first.matchWins++;
11440           } else {
11441             second.matchWins++;
11442           }
11443           break;
11444         case GameUnfinished:
11445           resChar = ' ';
11446         default:
11447           break;
11448         }
11449
11450         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11451         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11452             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11453             ReserveGame(nextGame, resChar); // sets nextGame
11454             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11455             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11456         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11457
11458         if (nextGame <= appData.matchGames && !abortMatch) {
11459             gameMode = nextGameMode;
11460             matchGame = nextGame; // this will be overruled in tourney mode!
11461             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11462             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11463             endingGame = 0; /* [HGM] crash */
11464             return;
11465         } else {
11466             gameMode = nextGameMode;
11467             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11468                      first.tidy, second.tidy,
11469                      first.matchWins, second.matchWins,
11470                      appData.matchGames - (first.matchWins + second.matchWins));
11471             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11472             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11473             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11474             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11475                 first.twoMachinesColor = "black\n";
11476                 second.twoMachinesColor = "white\n";
11477             } else {
11478                 first.twoMachinesColor = "white\n";
11479                 second.twoMachinesColor = "black\n";
11480             }
11481         }
11482     }
11483     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11484         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11485       ExitAnalyzeMode();
11486     gameMode = nextGameMode;
11487     ModeHighlight();
11488     endingGame = 0;  /* [HGM] crash */
11489     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11490         if(matchMode == TRUE) { // match through command line: exit with or without popup
11491             if(ranking) {
11492                 ToNrEvent(forwardMostMove);
11493                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11494                 else ExitEvent(0);
11495             } else DisplayFatalError(buf, 0, 0);
11496         } else { // match through menu; just stop, with or without popup
11497             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11498             ModeHighlight();
11499             if(ranking){
11500                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11501             } else DisplayNote(buf);
11502       }
11503       if(ranking) free(ranking);
11504     }
11505 }
11506
11507 /* Assumes program was just initialized (initString sent).
11508    Leaves program in force mode. */
11509 void
11510 FeedMovesToProgram (ChessProgramState *cps, int upto)
11511 {
11512     int i;
11513
11514     if (appData.debugMode)
11515       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11516               startedFromSetupPosition ? "position and " : "",
11517               backwardMostMove, upto, cps->which);
11518     if(currentlyInitializedVariant != gameInfo.variant) {
11519       char buf[MSG_SIZ];
11520         // [HGM] variantswitch: make engine aware of new variant
11521         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11522                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11523                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11524         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11525         SendToProgram(buf, cps);
11526         currentlyInitializedVariant = gameInfo.variant;
11527     }
11528     SendToProgram("force\n", cps);
11529     if (startedFromSetupPosition) {
11530         SendBoard(cps, backwardMostMove);
11531     if (appData.debugMode) {
11532         fprintf(debugFP, "feedMoves\n");
11533     }
11534     }
11535     for (i = backwardMostMove; i < upto; i++) {
11536         SendMoveToProgram(i, cps);
11537     }
11538 }
11539
11540
11541 int
11542 ResurrectChessProgram ()
11543 {
11544      /* The chess program may have exited.
11545         If so, restart it and feed it all the moves made so far. */
11546     static int doInit = 0;
11547
11548     if (appData.noChessProgram) return 1;
11549
11550     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11551         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11552         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11553         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11554     } else {
11555         if (first.pr != NoProc) return 1;
11556         StartChessProgram(&first);
11557     }
11558     InitChessProgram(&first, FALSE);
11559     FeedMovesToProgram(&first, currentMove);
11560
11561     if (!first.sendTime) {
11562         /* can't tell gnuchess what its clock should read,
11563            so we bow to its notion. */
11564         ResetClocks();
11565         timeRemaining[0][currentMove] = whiteTimeRemaining;
11566         timeRemaining[1][currentMove] = blackTimeRemaining;
11567     }
11568
11569     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11570                 appData.icsEngineAnalyze) && first.analysisSupport) {
11571       SendToProgram("analyze\n", &first);
11572       first.analyzing = TRUE;
11573     }
11574     return 1;
11575 }
11576
11577 /*
11578  * Button procedures
11579  */
11580 void
11581 Reset (int redraw, int init)
11582 {
11583     int i;
11584
11585     if (appData.debugMode) {
11586         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11587                 redraw, init, gameMode);
11588     }
11589     CleanupTail(); // [HGM] vari: delete any stored variations
11590     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11591     pausing = pauseExamInvalid = FALSE;
11592     startedFromSetupPosition = blackPlaysFirst = FALSE;
11593     firstMove = TRUE;
11594     whiteFlag = blackFlag = FALSE;
11595     userOfferedDraw = FALSE;
11596     hintRequested = bookRequested = FALSE;
11597     first.maybeThinking = FALSE;
11598     second.maybeThinking = FALSE;
11599     first.bookSuspend = FALSE; // [HGM] book
11600     second.bookSuspend = FALSE;
11601     thinkOutput[0] = NULLCHAR;
11602     lastHint[0] = NULLCHAR;
11603     ClearGameInfo(&gameInfo);
11604     gameInfo.variant = StringToVariant(appData.variant);
11605     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11606     ics_user_moved = ics_clock_paused = FALSE;
11607     ics_getting_history = H_FALSE;
11608     ics_gamenum = -1;
11609     white_holding[0] = black_holding[0] = NULLCHAR;
11610     ClearProgramStats();
11611     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11612
11613     ResetFrontEnd();
11614     ClearHighlights();
11615     flipView = appData.flipView;
11616     ClearPremoveHighlights();
11617     gotPremove = FALSE;
11618     alarmSounded = FALSE;
11619     killX = killY = -1; // [HGM] lion
11620
11621     GameEnds(EndOfFile, NULL, GE_PLAYER);
11622     if(appData.serverMovesName != NULL) {
11623         /* [HGM] prepare to make moves file for broadcasting */
11624         clock_t t = clock();
11625         if(serverMoves != NULL) fclose(serverMoves);
11626         serverMoves = fopen(appData.serverMovesName, "r");
11627         if(serverMoves != NULL) {
11628             fclose(serverMoves);
11629             /* delay 15 sec before overwriting, so all clients can see end */
11630             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11631         }
11632         serverMoves = fopen(appData.serverMovesName, "w");
11633     }
11634
11635     ExitAnalyzeMode();
11636     gameMode = BeginningOfGame;
11637     ModeHighlight();
11638     if(appData.icsActive) gameInfo.variant = VariantNormal;
11639     currentMove = forwardMostMove = backwardMostMove = 0;
11640     MarkTargetSquares(1);
11641     InitPosition(redraw);
11642     for (i = 0; i < MAX_MOVES; i++) {
11643         if (commentList[i] != NULL) {
11644             free(commentList[i]);
11645             commentList[i] = NULL;
11646         }
11647     }
11648     ResetClocks();
11649     timeRemaining[0][0] = whiteTimeRemaining;
11650     timeRemaining[1][0] = blackTimeRemaining;
11651
11652     if (first.pr == NoProc) {
11653         StartChessProgram(&first);
11654     }
11655     if (init) {
11656             InitChessProgram(&first, startedFromSetupPosition);
11657     }
11658     DisplayTitle("");
11659     DisplayMessage("", "");
11660     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11661     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11662     ClearMap();        // [HGM] exclude: invalidate map
11663 }
11664
11665 void
11666 AutoPlayGameLoop ()
11667 {
11668     for (;;) {
11669         if (!AutoPlayOneMove())
11670           return;
11671         if (matchMode || appData.timeDelay == 0)
11672           continue;
11673         if (appData.timeDelay < 0)
11674           return;
11675         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11676         break;
11677     }
11678 }
11679
11680 void
11681 AnalyzeNextGame()
11682 {
11683     ReloadGame(1); // next game
11684 }
11685
11686 int
11687 AutoPlayOneMove ()
11688 {
11689     int fromX, fromY, toX, toY;
11690
11691     if (appData.debugMode) {
11692       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11693     }
11694
11695     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11696       return FALSE;
11697
11698     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11699       pvInfoList[currentMove].depth = programStats.depth;
11700       pvInfoList[currentMove].score = programStats.score;
11701       pvInfoList[currentMove].time  = 0;
11702       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11703       else { // append analysis of final position as comment
11704         char buf[MSG_SIZ];
11705         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11706         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11707       }
11708       programStats.depth = 0;
11709     }
11710
11711     if (currentMove >= forwardMostMove) {
11712       if(gameMode == AnalyzeFile) {
11713           if(appData.loadGameIndex == -1) {
11714             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11715           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11716           } else {
11717           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11718         }
11719       }
11720 //      gameMode = EndOfGame;
11721 //      ModeHighlight();
11722
11723       /* [AS] Clear current move marker at the end of a game */
11724       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11725
11726       return FALSE;
11727     }
11728
11729     toX = moveList[currentMove][2] - AAA;
11730     toY = moveList[currentMove][3] - ONE;
11731
11732     if (moveList[currentMove][1] == '@') {
11733         if (appData.highlightLastMove) {
11734             SetHighlights(-1, -1, toX, toY);
11735         }
11736     } else {
11737         fromX = moveList[currentMove][0] - AAA;
11738         fromY = moveList[currentMove][1] - ONE;
11739
11740         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11741
11742         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11743
11744         if (appData.highlightLastMove) {
11745             SetHighlights(fromX, fromY, toX, toY);
11746         }
11747     }
11748     DisplayMove(currentMove);
11749     SendMoveToProgram(currentMove++, &first);
11750     DisplayBothClocks();
11751     DrawPosition(FALSE, boards[currentMove]);
11752     // [HGM] PV info: always display, routine tests if empty
11753     DisplayComment(currentMove - 1, commentList[currentMove]);
11754     return TRUE;
11755 }
11756
11757
11758 int
11759 LoadGameOneMove (ChessMove readAhead)
11760 {
11761     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11762     char promoChar = NULLCHAR;
11763     ChessMove moveType;
11764     char move[MSG_SIZ];
11765     char *p, *q;
11766
11767     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11768         gameMode != AnalyzeMode && gameMode != Training) {
11769         gameFileFP = NULL;
11770         return FALSE;
11771     }
11772
11773     yyboardindex = forwardMostMove;
11774     if (readAhead != EndOfFile) {
11775       moveType = readAhead;
11776     } else {
11777       if (gameFileFP == NULL)
11778           return FALSE;
11779       moveType = (ChessMove) Myylex();
11780     }
11781
11782     done = FALSE;
11783     switch (moveType) {
11784       case Comment:
11785         if (appData.debugMode)
11786           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11787         p = yy_text;
11788
11789         /* append the comment but don't display it */
11790         AppendComment(currentMove, p, FALSE);
11791         return TRUE;
11792
11793       case WhiteCapturesEnPassant:
11794       case BlackCapturesEnPassant:
11795       case WhitePromotion:
11796       case BlackPromotion:
11797       case WhiteNonPromotion:
11798       case BlackNonPromotion:
11799       case NormalMove:
11800       case FirstLeg:
11801       case WhiteKingSideCastle:
11802       case WhiteQueenSideCastle:
11803       case BlackKingSideCastle:
11804       case BlackQueenSideCastle:
11805       case WhiteKingSideCastleWild:
11806       case WhiteQueenSideCastleWild:
11807       case BlackKingSideCastleWild:
11808       case BlackQueenSideCastleWild:
11809       /* PUSH Fabien */
11810       case WhiteHSideCastleFR:
11811       case WhiteASideCastleFR:
11812       case BlackHSideCastleFR:
11813       case BlackASideCastleFR:
11814       /* POP Fabien */
11815         if (appData.debugMode)
11816           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11817         fromX = currentMoveString[0] - AAA;
11818         fromY = currentMoveString[1] - ONE;
11819         toX = currentMoveString[2] - AAA;
11820         toY = currentMoveString[3] - ONE;
11821         promoChar = currentMoveString[4];
11822         if(promoChar == ';') promoChar = NULLCHAR;
11823         break;
11824
11825       case WhiteDrop:
11826       case BlackDrop:
11827         if (appData.debugMode)
11828           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11829         fromX = moveType == WhiteDrop ?
11830           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11831         (int) CharToPiece(ToLower(currentMoveString[0]));
11832         fromY = DROP_RANK;
11833         toX = currentMoveString[2] - AAA;
11834         toY = currentMoveString[3] - ONE;
11835         break;
11836
11837       case WhiteWins:
11838       case BlackWins:
11839       case GameIsDrawn:
11840       case GameUnfinished:
11841         if (appData.debugMode)
11842           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11843         p = strchr(yy_text, '{');
11844         if (p == NULL) p = strchr(yy_text, '(');
11845         if (p == NULL) {
11846             p = yy_text;
11847             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11848         } else {
11849             q = strchr(p, *p == '{' ? '}' : ')');
11850             if (q != NULL) *q = NULLCHAR;
11851             p++;
11852         }
11853         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11854         GameEnds(moveType, p, GE_FILE);
11855         done = TRUE;
11856         if (cmailMsgLoaded) {
11857             ClearHighlights();
11858             flipView = WhiteOnMove(currentMove);
11859             if (moveType == GameUnfinished) flipView = !flipView;
11860             if (appData.debugMode)
11861               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11862         }
11863         break;
11864
11865       case EndOfFile:
11866         if (appData.debugMode)
11867           fprintf(debugFP, "Parser hit end of file\n");
11868         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11869           case MT_NONE:
11870           case MT_CHECK:
11871             break;
11872           case MT_CHECKMATE:
11873           case MT_STAINMATE:
11874             if (WhiteOnMove(currentMove)) {
11875                 GameEnds(BlackWins, "Black mates", GE_FILE);
11876             } else {
11877                 GameEnds(WhiteWins, "White mates", GE_FILE);
11878             }
11879             break;
11880           case MT_STALEMATE:
11881             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11882             break;
11883         }
11884         done = TRUE;
11885         break;
11886
11887       case MoveNumberOne:
11888         if (lastLoadGameStart == GNUChessGame) {
11889             /* GNUChessGames have numbers, but they aren't move numbers */
11890             if (appData.debugMode)
11891               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11892                       yy_text, (int) moveType);
11893             return LoadGameOneMove(EndOfFile); /* tail recursion */
11894         }
11895         /* else fall thru */
11896
11897       case XBoardGame:
11898       case GNUChessGame:
11899       case PGNTag:
11900         /* Reached start of next game in file */
11901         if (appData.debugMode)
11902           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11903         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11904           case MT_NONE:
11905           case MT_CHECK:
11906             break;
11907           case MT_CHECKMATE:
11908           case MT_STAINMATE:
11909             if (WhiteOnMove(currentMove)) {
11910                 GameEnds(BlackWins, "Black mates", GE_FILE);
11911             } else {
11912                 GameEnds(WhiteWins, "White mates", GE_FILE);
11913             }
11914             break;
11915           case MT_STALEMATE:
11916             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11917             break;
11918         }
11919         done = TRUE;
11920         break;
11921
11922       case PositionDiagram:     /* should not happen; ignore */
11923       case ElapsedTime:         /* ignore */
11924       case NAG:                 /* ignore */
11925         if (appData.debugMode)
11926           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11927                   yy_text, (int) moveType);
11928         return LoadGameOneMove(EndOfFile); /* tail recursion */
11929
11930       case IllegalMove:
11931         if (appData.testLegality) {
11932             if (appData.debugMode)
11933               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11934             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11935                     (forwardMostMove / 2) + 1,
11936                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11937             DisplayError(move, 0);
11938             done = TRUE;
11939         } else {
11940             if (appData.debugMode)
11941               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11942                       yy_text, currentMoveString);
11943             fromX = currentMoveString[0] - AAA;
11944             fromY = currentMoveString[1] - ONE;
11945             toX = currentMoveString[2] - AAA;
11946             toY = currentMoveString[3] - ONE;
11947             promoChar = currentMoveString[4];
11948         }
11949         break;
11950
11951       case AmbiguousMove:
11952         if (appData.debugMode)
11953           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11954         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11955                 (forwardMostMove / 2) + 1,
11956                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11957         DisplayError(move, 0);
11958         done = TRUE;
11959         break;
11960
11961       default:
11962       case ImpossibleMove:
11963         if (appData.debugMode)
11964           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11965         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11966                 (forwardMostMove / 2) + 1,
11967                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11968         DisplayError(move, 0);
11969         done = TRUE;
11970         break;
11971     }
11972
11973     if (done) {
11974         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11975             DrawPosition(FALSE, boards[currentMove]);
11976             DisplayBothClocks();
11977             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11978               DisplayComment(currentMove - 1, commentList[currentMove]);
11979         }
11980         (void) StopLoadGameTimer();
11981         gameFileFP = NULL;
11982         cmailOldMove = forwardMostMove;
11983         return FALSE;
11984     } else {
11985         /* currentMoveString is set as a side-effect of yylex */
11986
11987         thinkOutput[0] = NULLCHAR;
11988         MakeMove(fromX, fromY, toX, toY, promoChar);
11989         killX = killY = -1; // [HGM] lion: used up
11990         currentMove = forwardMostMove;
11991         return TRUE;
11992     }
11993 }
11994
11995 /* Load the nth game from the given file */
11996 int
11997 LoadGameFromFile (char *filename, int n, char *title, int useList)
11998 {
11999     FILE *f;
12000     char buf[MSG_SIZ];
12001
12002     if (strcmp(filename, "-") == 0) {
12003         f = stdin;
12004         title = "stdin";
12005     } else {
12006         f = fopen(filename, "rb");
12007         if (f == NULL) {
12008           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12009             DisplayError(buf, errno);
12010             return FALSE;
12011         }
12012     }
12013     if (fseek(f, 0, 0) == -1) {
12014         /* f is not seekable; probably a pipe */
12015         useList = FALSE;
12016     }
12017     if (useList && n == 0) {
12018         int error = GameListBuild(f);
12019         if (error) {
12020             DisplayError(_("Cannot build game list"), error);
12021         } else if (!ListEmpty(&gameList) &&
12022                    ((ListGame *) gameList.tailPred)->number > 1) {
12023             GameListPopUp(f, title);
12024             return TRUE;
12025         }
12026         GameListDestroy();
12027         n = 1;
12028     }
12029     if (n == 0) n = 1;
12030     return LoadGame(f, n, title, FALSE);
12031 }
12032
12033
12034 void
12035 MakeRegisteredMove ()
12036 {
12037     int fromX, fromY, toX, toY;
12038     char promoChar;
12039     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12040         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12041           case CMAIL_MOVE:
12042           case CMAIL_DRAW:
12043             if (appData.debugMode)
12044               fprintf(debugFP, "Restoring %s for game %d\n",
12045                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12046
12047             thinkOutput[0] = NULLCHAR;
12048             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12049             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12050             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12051             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12052             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12053             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12054             MakeMove(fromX, fromY, toX, toY, promoChar);
12055             ShowMove(fromX, fromY, toX, toY);
12056
12057             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12058               case MT_NONE:
12059               case MT_CHECK:
12060                 break;
12061
12062               case MT_CHECKMATE:
12063               case MT_STAINMATE:
12064                 if (WhiteOnMove(currentMove)) {
12065                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12066                 } else {
12067                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12068                 }
12069                 break;
12070
12071               case MT_STALEMATE:
12072                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12073                 break;
12074             }
12075
12076             break;
12077
12078           case CMAIL_RESIGN:
12079             if (WhiteOnMove(currentMove)) {
12080                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12081             } else {
12082                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12083             }
12084             break;
12085
12086           case CMAIL_ACCEPT:
12087             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12088             break;
12089
12090           default:
12091             break;
12092         }
12093     }
12094
12095     return;
12096 }
12097
12098 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12099 int
12100 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12101 {
12102     int retVal;
12103
12104     if (gameNumber > nCmailGames) {
12105         DisplayError(_("No more games in this message"), 0);
12106         return FALSE;
12107     }
12108     if (f == lastLoadGameFP) {
12109         int offset = gameNumber - lastLoadGameNumber;
12110         if (offset == 0) {
12111             cmailMsg[0] = NULLCHAR;
12112             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12113                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12114                 nCmailMovesRegistered--;
12115             }
12116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12117             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12118                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12119             }
12120         } else {
12121             if (! RegisterMove()) return FALSE;
12122         }
12123     }
12124
12125     retVal = LoadGame(f, gameNumber, title, useList);
12126
12127     /* Make move registered during previous look at this game, if any */
12128     MakeRegisteredMove();
12129
12130     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12131         commentList[currentMove]
12132           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12133         DisplayComment(currentMove - 1, commentList[currentMove]);
12134     }
12135
12136     return retVal;
12137 }
12138
12139 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12140 int
12141 ReloadGame (int offset)
12142 {
12143     int gameNumber = lastLoadGameNumber + offset;
12144     if (lastLoadGameFP == NULL) {
12145         DisplayError(_("No game has been loaded yet"), 0);
12146         return FALSE;
12147     }
12148     if (gameNumber <= 0) {
12149         DisplayError(_("Can't back up any further"), 0);
12150         return FALSE;
12151     }
12152     if (cmailMsgLoaded) {
12153         return CmailLoadGame(lastLoadGameFP, gameNumber,
12154                              lastLoadGameTitle, lastLoadGameUseList);
12155     } else {
12156         return LoadGame(lastLoadGameFP, gameNumber,
12157                         lastLoadGameTitle, lastLoadGameUseList);
12158     }
12159 }
12160
12161 int keys[EmptySquare+1];
12162
12163 int
12164 PositionMatches (Board b1, Board b2)
12165 {
12166     int r, f, sum=0;
12167     switch(appData.searchMode) {
12168         case 1: return CompareWithRights(b1, b2);
12169         case 2:
12170             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12171                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12172             }
12173             return TRUE;
12174         case 3:
12175             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12176               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12177                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12178             }
12179             return sum==0;
12180         case 4:
12181             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12182                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12183             }
12184             return sum==0;
12185     }
12186     return TRUE;
12187 }
12188
12189 #define Q_PROMO  4
12190 #define Q_EP     3
12191 #define Q_BCASTL 2
12192 #define Q_WCASTL 1
12193
12194 int pieceList[256], quickBoard[256];
12195 ChessSquare pieceType[256] = { EmptySquare };
12196 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12197 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12198 int soughtTotal, turn;
12199 Boolean epOK, flipSearch;
12200
12201 typedef struct {
12202     unsigned char piece, to;
12203 } Move;
12204
12205 #define DSIZE (250000)
12206
12207 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12208 Move *moveDatabase = initialSpace;
12209 unsigned int movePtr, dataSize = DSIZE;
12210
12211 int
12212 MakePieceList (Board board, int *counts)
12213 {
12214     int r, f, n=Q_PROMO, total=0;
12215     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12216     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12217         int sq = f + (r<<4);
12218         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12219             quickBoard[sq] = ++n;
12220             pieceList[n] = sq;
12221             pieceType[n] = board[r][f];
12222             counts[board[r][f]]++;
12223             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12224             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12225             total++;
12226         }
12227     }
12228     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12229     return total;
12230 }
12231
12232 void
12233 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12234 {
12235     int sq = fromX + (fromY<<4);
12236     int piece = quickBoard[sq], rook;
12237     quickBoard[sq] = 0;
12238     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12239     if(piece == pieceList[1] && fromY == toY) {
12240       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12241         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12242         moveDatabase[movePtr++].piece = Q_WCASTL;
12243         quickBoard[sq] = piece;
12244         piece = quickBoard[from]; quickBoard[from] = 0;
12245         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12246       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12247         quickBoard[sq] = 0; // remove Rook
12248         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12249         moveDatabase[movePtr++].piece = Q_WCASTL;
12250         quickBoard[sq] = pieceList[1]; // put King
12251         piece = rook;
12252         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12253       }
12254     } else
12255     if(piece == pieceList[2] && fromY == toY) {
12256       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12257         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12258         moveDatabase[movePtr++].piece = Q_BCASTL;
12259         quickBoard[sq] = piece;
12260         piece = quickBoard[from]; quickBoard[from] = 0;
12261         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12262       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12263         quickBoard[sq] = 0; // remove Rook
12264         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12265         moveDatabase[movePtr++].piece = Q_BCASTL;
12266         quickBoard[sq] = pieceList[2]; // put King
12267         piece = rook;
12268         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12269       }
12270     } else
12271     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12272         quickBoard[(fromY<<4)+toX] = 0;
12273         moveDatabase[movePtr].piece = Q_EP;
12274         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12275         moveDatabase[movePtr].to = sq;
12276     } else
12277     if(promoPiece != pieceType[piece]) {
12278         moveDatabase[movePtr++].piece = Q_PROMO;
12279         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12280     }
12281     moveDatabase[movePtr].piece = piece;
12282     quickBoard[sq] = piece;
12283     movePtr++;
12284 }
12285
12286 int
12287 PackGame (Board board)
12288 {
12289     Move *newSpace = NULL;
12290     moveDatabase[movePtr].piece = 0; // terminate previous game
12291     if(movePtr > dataSize) {
12292         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12293         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12294         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12295         if(newSpace) {
12296             int i;
12297             Move *p = moveDatabase, *q = newSpace;
12298             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12299             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12300             moveDatabase = newSpace;
12301         } else { // calloc failed, we must be out of memory. Too bad...
12302             dataSize = 0; // prevent calloc events for all subsequent games
12303             return 0;     // and signal this one isn't cached
12304         }
12305     }
12306     movePtr++;
12307     MakePieceList(board, counts);
12308     return movePtr;
12309 }
12310
12311 int
12312 QuickCompare (Board board, int *minCounts, int *maxCounts)
12313 {   // compare according to search mode
12314     int r, f;
12315     switch(appData.searchMode)
12316     {
12317       case 1: // exact position match
12318         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12319         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12320             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12321         }
12322         break;
12323       case 2: // can have extra material on empty squares
12324         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12325             if(board[r][f] == EmptySquare) continue;
12326             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12327         }
12328         break;
12329       case 3: // material with exact Pawn structure
12330         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12331             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12332             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12333         } // fall through to material comparison
12334       case 4: // exact material
12335         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12336         break;
12337       case 6: // material range with given imbalance
12338         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12339         // fall through to range comparison
12340       case 5: // material range
12341         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12342     }
12343     return TRUE;
12344 }
12345
12346 int
12347 QuickScan (Board board, Move *move)
12348 {   // reconstruct game,and compare all positions in it
12349     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12350     do {
12351         int piece = move->piece;
12352         int to = move->to, from = pieceList[piece];
12353         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12354           if(!piece) return -1;
12355           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12356             piece = (++move)->piece;
12357             from = pieceList[piece];
12358             counts[pieceType[piece]]--;
12359             pieceType[piece] = (ChessSquare) move->to;
12360             counts[move->to]++;
12361           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12362             counts[pieceType[quickBoard[to]]]--;
12363             quickBoard[to] = 0; total--;
12364             move++;
12365             continue;
12366           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12367             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12368             from  = pieceList[piece]; // so this must be King
12369             quickBoard[from] = 0;
12370             pieceList[piece] = to;
12371             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12372             quickBoard[from] = 0; // rook
12373             quickBoard[to] = piece;
12374             to = move->to; piece = move->piece;
12375             goto aftercastle;
12376           }
12377         }
12378         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12379         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12380         quickBoard[from] = 0;
12381       aftercastle:
12382         quickBoard[to] = piece;
12383         pieceList[piece] = to;
12384         cnt++; turn ^= 3;
12385         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12386            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12387            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12388                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12389           ) {
12390             static int lastCounts[EmptySquare+1];
12391             int i;
12392             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12393             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12394         } else stretch = 0;
12395         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12396         move++;
12397     } while(1);
12398 }
12399
12400 void
12401 InitSearch ()
12402 {
12403     int r, f;
12404     flipSearch = FALSE;
12405     CopyBoard(soughtBoard, boards[currentMove]);
12406     soughtTotal = MakePieceList(soughtBoard, maxSought);
12407     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12408     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12409     CopyBoard(reverseBoard, boards[currentMove]);
12410     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12411         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12412         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12413         reverseBoard[r][f] = piece;
12414     }
12415     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12416     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12417     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12418                  || (boards[currentMove][CASTLING][2] == NoRights ||
12419                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12420                  && (boards[currentMove][CASTLING][5] == NoRights ||
12421                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12422       ) {
12423         flipSearch = TRUE;
12424         CopyBoard(flipBoard, soughtBoard);
12425         CopyBoard(rotateBoard, reverseBoard);
12426         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12427             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12428             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12429         }
12430     }
12431     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12432     if(appData.searchMode >= 5) {
12433         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12434         MakePieceList(soughtBoard, minSought);
12435         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12436     }
12437     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12438         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12439 }
12440
12441 GameInfo dummyInfo;
12442 static int creatingBook;
12443
12444 int
12445 GameContainsPosition (FILE *f, ListGame *lg)
12446 {
12447     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12448     int fromX, fromY, toX, toY;
12449     char promoChar;
12450     static int initDone=FALSE;
12451
12452     // weed out games based on numerical tag comparison
12453     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12454     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12455     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12456     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12457     if(!initDone) {
12458         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12459         initDone = TRUE;
12460     }
12461     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12462     else CopyBoard(boards[scratch], initialPosition); // default start position
12463     if(lg->moves) {
12464         turn = btm + 1;
12465         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12466         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12467     }
12468     if(btm) plyNr++;
12469     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12470     fseek(f, lg->offset, 0);
12471     yynewfile(f);
12472     while(1) {
12473         yyboardindex = scratch;
12474         quickFlag = plyNr+1;
12475         next = Myylex();
12476         quickFlag = 0;
12477         switch(next) {
12478             case PGNTag:
12479                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12480             default:
12481                 continue;
12482
12483             case XBoardGame:
12484             case GNUChessGame:
12485                 if(plyNr) return -1; // after we have seen moves, this is for new game
12486               continue;
12487
12488             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12489             case ImpossibleMove:
12490             case WhiteWins: // game ends here with these four
12491             case BlackWins:
12492             case GameIsDrawn:
12493             case GameUnfinished:
12494                 return -1;
12495
12496             case IllegalMove:
12497                 if(appData.testLegality) return -1;
12498             case WhiteCapturesEnPassant:
12499             case BlackCapturesEnPassant:
12500             case WhitePromotion:
12501             case BlackPromotion:
12502             case WhiteNonPromotion:
12503             case BlackNonPromotion:
12504             case NormalMove:
12505             case FirstLeg:
12506             case WhiteKingSideCastle:
12507             case WhiteQueenSideCastle:
12508             case BlackKingSideCastle:
12509             case BlackQueenSideCastle:
12510             case WhiteKingSideCastleWild:
12511             case WhiteQueenSideCastleWild:
12512             case BlackKingSideCastleWild:
12513             case BlackQueenSideCastleWild:
12514             case WhiteHSideCastleFR:
12515             case WhiteASideCastleFR:
12516             case BlackHSideCastleFR:
12517             case BlackASideCastleFR:
12518                 fromX = currentMoveString[0] - AAA;
12519                 fromY = currentMoveString[1] - ONE;
12520                 toX = currentMoveString[2] - AAA;
12521                 toY = currentMoveString[3] - ONE;
12522                 promoChar = currentMoveString[4];
12523                 break;
12524             case WhiteDrop:
12525             case BlackDrop:
12526                 fromX = next == WhiteDrop ?
12527                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12528                   (int) CharToPiece(ToLower(currentMoveString[0]));
12529                 fromY = DROP_RANK;
12530                 toX = currentMoveString[2] - AAA;
12531                 toY = currentMoveString[3] - ONE;
12532                 promoChar = 0;
12533                 break;
12534         }
12535         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12536         plyNr++;
12537         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12538         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12539         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12540         if(appData.findMirror) {
12541             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12542             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12543         }
12544     }
12545 }
12546
12547 /* Load the nth game from open file f */
12548 int
12549 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12550 {
12551     ChessMove cm;
12552     char buf[MSG_SIZ];
12553     int gn = gameNumber;
12554     ListGame *lg = NULL;
12555     int numPGNTags = 0;
12556     int err, pos = -1;
12557     GameMode oldGameMode;
12558     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12559
12560     if (appData.debugMode)
12561         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12562
12563     if (gameMode == Training )
12564         SetTrainingModeOff();
12565
12566     oldGameMode = gameMode;
12567     if (gameMode != BeginningOfGame) {
12568       Reset(FALSE, TRUE);
12569     }
12570     killX = killY = -1; // [HGM] lion: in case we did not Reset
12571
12572     gameFileFP = f;
12573     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12574         fclose(lastLoadGameFP);
12575     }
12576
12577     if (useList) {
12578         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12579
12580         if (lg) {
12581             fseek(f, lg->offset, 0);
12582             GameListHighlight(gameNumber);
12583             pos = lg->position;
12584             gn = 1;
12585         }
12586         else {
12587             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12588               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12589             else
12590             DisplayError(_("Game number out of range"), 0);
12591             return FALSE;
12592         }
12593     } else {
12594         GameListDestroy();
12595         if (fseek(f, 0, 0) == -1) {
12596             if (f == lastLoadGameFP ?
12597                 gameNumber == lastLoadGameNumber + 1 :
12598                 gameNumber == 1) {
12599                 gn = 1;
12600             } else {
12601                 DisplayError(_("Can't seek on game file"), 0);
12602                 return FALSE;
12603             }
12604         }
12605     }
12606     lastLoadGameFP = f;
12607     lastLoadGameNumber = gameNumber;
12608     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12609     lastLoadGameUseList = useList;
12610
12611     yynewfile(f);
12612
12613     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12614       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12615                 lg->gameInfo.black);
12616             DisplayTitle(buf);
12617     } else if (*title != NULLCHAR) {
12618         if (gameNumber > 1) {
12619           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12620             DisplayTitle(buf);
12621         } else {
12622             DisplayTitle(title);
12623         }
12624     }
12625
12626     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12627         gameMode = PlayFromGameFile;
12628         ModeHighlight();
12629     }
12630
12631     currentMove = forwardMostMove = backwardMostMove = 0;
12632     CopyBoard(boards[0], initialPosition);
12633     StopClocks();
12634
12635     /*
12636      * Skip the first gn-1 games in the file.
12637      * Also skip over anything that precedes an identifiable
12638      * start of game marker, to avoid being confused by
12639      * garbage at the start of the file.  Currently
12640      * recognized start of game markers are the move number "1",
12641      * the pattern "gnuchess .* game", the pattern
12642      * "^[#;%] [^ ]* game file", and a PGN tag block.
12643      * A game that starts with one of the latter two patterns
12644      * will also have a move number 1, possibly
12645      * following a position diagram.
12646      * 5-4-02: Let's try being more lenient and allowing a game to
12647      * start with an unnumbered move.  Does that break anything?
12648      */
12649     cm = lastLoadGameStart = EndOfFile;
12650     while (gn > 0) {
12651         yyboardindex = forwardMostMove;
12652         cm = (ChessMove) Myylex();
12653         switch (cm) {
12654           case EndOfFile:
12655             if (cmailMsgLoaded) {
12656                 nCmailGames = CMAIL_MAX_GAMES - gn;
12657             } else {
12658                 Reset(TRUE, TRUE);
12659                 DisplayError(_("Game not found in file"), 0);
12660             }
12661             return FALSE;
12662
12663           case GNUChessGame:
12664           case XBoardGame:
12665             gn--;
12666             lastLoadGameStart = cm;
12667             break;
12668
12669           case MoveNumberOne:
12670             switch (lastLoadGameStart) {
12671               case GNUChessGame:
12672               case XBoardGame:
12673               case PGNTag:
12674                 break;
12675               case MoveNumberOne:
12676               case EndOfFile:
12677                 gn--;           /* count this game */
12678                 lastLoadGameStart = cm;
12679                 break;
12680               default:
12681                 /* impossible */
12682                 break;
12683             }
12684             break;
12685
12686           case PGNTag:
12687             switch (lastLoadGameStart) {
12688               case GNUChessGame:
12689               case PGNTag:
12690               case MoveNumberOne:
12691               case EndOfFile:
12692                 gn--;           /* count this game */
12693                 lastLoadGameStart = cm;
12694                 break;
12695               case XBoardGame:
12696                 lastLoadGameStart = cm; /* game counted already */
12697                 break;
12698               default:
12699                 /* impossible */
12700                 break;
12701             }
12702             if (gn > 0) {
12703                 do {
12704                     yyboardindex = forwardMostMove;
12705                     cm = (ChessMove) Myylex();
12706                 } while (cm == PGNTag || cm == Comment);
12707             }
12708             break;
12709
12710           case WhiteWins:
12711           case BlackWins:
12712           case GameIsDrawn:
12713             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12714                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12715                     != CMAIL_OLD_RESULT) {
12716                     nCmailResults ++ ;
12717                     cmailResult[  CMAIL_MAX_GAMES
12718                                 - gn - 1] = CMAIL_OLD_RESULT;
12719                 }
12720             }
12721             break;
12722
12723           case NormalMove:
12724           case FirstLeg:
12725             /* Only a NormalMove can be at the start of a game
12726              * without a position diagram. */
12727             if (lastLoadGameStart == EndOfFile ) {
12728               gn--;
12729               lastLoadGameStart = MoveNumberOne;
12730             }
12731             break;
12732
12733           default:
12734             break;
12735         }
12736     }
12737
12738     if (appData.debugMode)
12739       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12740
12741     if (cm == XBoardGame) {
12742         /* Skip any header junk before position diagram and/or move 1 */
12743         for (;;) {
12744             yyboardindex = forwardMostMove;
12745             cm = (ChessMove) Myylex();
12746
12747             if (cm == EndOfFile ||
12748                 cm == GNUChessGame || cm == XBoardGame) {
12749                 /* Empty game; pretend end-of-file and handle later */
12750                 cm = EndOfFile;
12751                 break;
12752             }
12753
12754             if (cm == MoveNumberOne || cm == PositionDiagram ||
12755                 cm == PGNTag || cm == Comment)
12756               break;
12757         }
12758     } else if (cm == GNUChessGame) {
12759         if (gameInfo.event != NULL) {
12760             free(gameInfo.event);
12761         }
12762         gameInfo.event = StrSave(yy_text);
12763     }
12764
12765     startedFromSetupPosition = FALSE;
12766     while (cm == PGNTag) {
12767         if (appData.debugMode)
12768           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12769         err = ParsePGNTag(yy_text, &gameInfo);
12770         if (!err) numPGNTags++;
12771
12772         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12773         if(gameInfo.variant != oldVariant) {
12774             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12775             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12776             InitPosition(TRUE);
12777             oldVariant = gameInfo.variant;
12778             if (appData.debugMode)
12779               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12780         }
12781
12782
12783         if (gameInfo.fen != NULL) {
12784           Board initial_position;
12785           startedFromSetupPosition = TRUE;
12786           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12787             Reset(TRUE, TRUE);
12788             DisplayError(_("Bad FEN position in file"), 0);
12789             return FALSE;
12790           }
12791           CopyBoard(boards[0], initial_position);
12792           if (blackPlaysFirst) {
12793             currentMove = forwardMostMove = backwardMostMove = 1;
12794             CopyBoard(boards[1], initial_position);
12795             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12796             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12797             timeRemaining[0][1] = whiteTimeRemaining;
12798             timeRemaining[1][1] = blackTimeRemaining;
12799             if (commentList[0] != NULL) {
12800               commentList[1] = commentList[0];
12801               commentList[0] = NULL;
12802             }
12803           } else {
12804             currentMove = forwardMostMove = backwardMostMove = 0;
12805           }
12806           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12807           {   int i;
12808               initialRulePlies = FENrulePlies;
12809               for( i=0; i< nrCastlingRights; i++ )
12810                   initialRights[i] = initial_position[CASTLING][i];
12811           }
12812           yyboardindex = forwardMostMove;
12813           free(gameInfo.fen);
12814           gameInfo.fen = NULL;
12815         }
12816
12817         yyboardindex = forwardMostMove;
12818         cm = (ChessMove) Myylex();
12819
12820         /* Handle comments interspersed among the tags */
12821         while (cm == Comment) {
12822             char *p;
12823             if (appData.debugMode)
12824               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12825             p = yy_text;
12826             AppendComment(currentMove, p, FALSE);
12827             yyboardindex = forwardMostMove;
12828             cm = (ChessMove) Myylex();
12829         }
12830     }
12831
12832     /* don't rely on existence of Event tag since if game was
12833      * pasted from clipboard the Event tag may not exist
12834      */
12835     if (numPGNTags > 0){
12836         char *tags;
12837         if (gameInfo.variant == VariantNormal) {
12838           VariantClass v = StringToVariant(gameInfo.event);
12839           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12840           if(v < VariantShogi) gameInfo.variant = v;
12841         }
12842         if (!matchMode) {
12843           if( appData.autoDisplayTags ) {
12844             tags = PGNTags(&gameInfo);
12845             TagsPopUp(tags, CmailMsg());
12846             free(tags);
12847           }
12848         }
12849     } else {
12850         /* Make something up, but don't display it now */
12851         SetGameInfo();
12852         TagsPopDown();
12853     }
12854
12855     if (cm == PositionDiagram) {
12856         int i, j;
12857         char *p;
12858         Board initial_position;
12859
12860         if (appData.debugMode)
12861           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12862
12863         if (!startedFromSetupPosition) {
12864             p = yy_text;
12865             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12866               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12867                 switch (*p) {
12868                   case '{':
12869                   case '[':
12870                   case '-':
12871                   case ' ':
12872                   case '\t':
12873                   case '\n':
12874                   case '\r':
12875                     break;
12876                   default:
12877                     initial_position[i][j++] = CharToPiece(*p);
12878                     break;
12879                 }
12880             while (*p == ' ' || *p == '\t' ||
12881                    *p == '\n' || *p == '\r') p++;
12882
12883             if (strncmp(p, "black", strlen("black"))==0)
12884               blackPlaysFirst = TRUE;
12885             else
12886               blackPlaysFirst = FALSE;
12887             startedFromSetupPosition = TRUE;
12888
12889             CopyBoard(boards[0], initial_position);
12890             if (blackPlaysFirst) {
12891                 currentMove = forwardMostMove = backwardMostMove = 1;
12892                 CopyBoard(boards[1], initial_position);
12893                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12894                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12895                 timeRemaining[0][1] = whiteTimeRemaining;
12896                 timeRemaining[1][1] = blackTimeRemaining;
12897                 if (commentList[0] != NULL) {
12898                     commentList[1] = commentList[0];
12899                     commentList[0] = NULL;
12900                 }
12901             } else {
12902                 currentMove = forwardMostMove = backwardMostMove = 0;
12903             }
12904         }
12905         yyboardindex = forwardMostMove;
12906         cm = (ChessMove) Myylex();
12907     }
12908
12909   if(!creatingBook) {
12910     if (first.pr == NoProc) {
12911         StartChessProgram(&first);
12912     }
12913     InitChessProgram(&first, FALSE);
12914     SendToProgram("force\n", &first);
12915     if (startedFromSetupPosition) {
12916         SendBoard(&first, forwardMostMove);
12917     if (appData.debugMode) {
12918         fprintf(debugFP, "Load Game\n");
12919     }
12920         DisplayBothClocks();
12921     }
12922   }
12923
12924     /* [HGM] server: flag to write setup moves in broadcast file as one */
12925     loadFlag = appData.suppressLoadMoves;
12926
12927     while (cm == Comment) {
12928         char *p;
12929         if (appData.debugMode)
12930           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12931         p = yy_text;
12932         AppendComment(currentMove, p, FALSE);
12933         yyboardindex = forwardMostMove;
12934         cm = (ChessMove) Myylex();
12935     }
12936
12937     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12938         cm == WhiteWins || cm == BlackWins ||
12939         cm == GameIsDrawn || cm == GameUnfinished) {
12940         DisplayMessage("", _("No moves in game"));
12941         if (cmailMsgLoaded) {
12942             if (appData.debugMode)
12943               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12944             ClearHighlights();
12945             flipView = FALSE;
12946         }
12947         DrawPosition(FALSE, boards[currentMove]);
12948         DisplayBothClocks();
12949         gameMode = EditGame;
12950         ModeHighlight();
12951         gameFileFP = NULL;
12952         cmailOldMove = 0;
12953         return TRUE;
12954     }
12955
12956     // [HGM] PV info: routine tests if comment empty
12957     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12958         DisplayComment(currentMove - 1, commentList[currentMove]);
12959     }
12960     if (!matchMode && appData.timeDelay != 0)
12961       DrawPosition(FALSE, boards[currentMove]);
12962
12963     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12964       programStats.ok_to_send = 1;
12965     }
12966
12967     /* if the first token after the PGN tags is a move
12968      * and not move number 1, retrieve it from the parser
12969      */
12970     if (cm != MoveNumberOne)
12971         LoadGameOneMove(cm);
12972
12973     /* load the remaining moves from the file */
12974     while (LoadGameOneMove(EndOfFile)) {
12975       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12976       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12977     }
12978
12979     /* rewind to the start of the game */
12980     currentMove = backwardMostMove;
12981
12982     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12983
12984     if (oldGameMode == AnalyzeFile) {
12985       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12986       AnalyzeFileEvent();
12987     } else
12988     if (oldGameMode == AnalyzeMode) {
12989       AnalyzeFileEvent();
12990     }
12991
12992     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12993         long int w, b; // [HGM] adjourn: restore saved clock times
12994         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12995         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12996             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12997             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12998         }
12999     }
13000
13001     if(creatingBook) return TRUE;
13002     if (!matchMode && pos > 0) {
13003         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13004     } else
13005     if (matchMode || appData.timeDelay == 0) {
13006       ToEndEvent();
13007     } else if (appData.timeDelay > 0) {
13008       AutoPlayGameLoop();
13009     }
13010
13011     if (appData.debugMode)
13012         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13013
13014     loadFlag = 0; /* [HGM] true game starts */
13015     return TRUE;
13016 }
13017
13018 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13019 int
13020 ReloadPosition (int offset)
13021 {
13022     int positionNumber = lastLoadPositionNumber + offset;
13023     if (lastLoadPositionFP == NULL) {
13024         DisplayError(_("No position has been loaded yet"), 0);
13025         return FALSE;
13026     }
13027     if (positionNumber <= 0) {
13028         DisplayError(_("Can't back up any further"), 0);
13029         return FALSE;
13030     }
13031     return LoadPosition(lastLoadPositionFP, positionNumber,
13032                         lastLoadPositionTitle);
13033 }
13034
13035 /* Load the nth position from the given file */
13036 int
13037 LoadPositionFromFile (char *filename, int n, char *title)
13038 {
13039     FILE *f;
13040     char buf[MSG_SIZ];
13041
13042     if (strcmp(filename, "-") == 0) {
13043         return LoadPosition(stdin, n, "stdin");
13044     } else {
13045         f = fopen(filename, "rb");
13046         if (f == NULL) {
13047             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13048             DisplayError(buf, errno);
13049             return FALSE;
13050         } else {
13051             return LoadPosition(f, n, title);
13052         }
13053     }
13054 }
13055
13056 /* Load the nth position from the given open file, and close it */
13057 int
13058 LoadPosition (FILE *f, int positionNumber, char *title)
13059 {
13060     char *p, line[MSG_SIZ];
13061     Board initial_position;
13062     int i, j, fenMode, pn;
13063
13064     if (gameMode == Training )
13065         SetTrainingModeOff();
13066
13067     if (gameMode != BeginningOfGame) {
13068         Reset(FALSE, TRUE);
13069     }
13070     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13071         fclose(lastLoadPositionFP);
13072     }
13073     if (positionNumber == 0) positionNumber = 1;
13074     lastLoadPositionFP = f;
13075     lastLoadPositionNumber = positionNumber;
13076     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13077     if (first.pr == NoProc && !appData.noChessProgram) {
13078       StartChessProgram(&first);
13079       InitChessProgram(&first, FALSE);
13080     }
13081     pn = positionNumber;
13082     if (positionNumber < 0) {
13083         /* Negative position number means to seek to that byte offset */
13084         if (fseek(f, -positionNumber, 0) == -1) {
13085             DisplayError(_("Can't seek on position file"), 0);
13086             return FALSE;
13087         };
13088         pn = 1;
13089     } else {
13090         if (fseek(f, 0, 0) == -1) {
13091             if (f == lastLoadPositionFP ?
13092                 positionNumber == lastLoadPositionNumber + 1 :
13093                 positionNumber == 1) {
13094                 pn = 1;
13095             } else {
13096                 DisplayError(_("Can't seek on position file"), 0);
13097                 return FALSE;
13098             }
13099         }
13100     }
13101     /* See if this file is FEN or old-style xboard */
13102     if (fgets(line, MSG_SIZ, f) == NULL) {
13103         DisplayError(_("Position not found in file"), 0);
13104         return FALSE;
13105     }
13106     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13107     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13108
13109     if (pn >= 2) {
13110         if (fenMode || line[0] == '#') pn--;
13111         while (pn > 0) {
13112             /* skip positions before number pn */
13113             if (fgets(line, MSG_SIZ, f) == NULL) {
13114                 Reset(TRUE, TRUE);
13115                 DisplayError(_("Position not found in file"), 0);
13116                 return FALSE;
13117             }
13118             if (fenMode || line[0] == '#') pn--;
13119         }
13120     }
13121
13122     if (fenMode) {
13123         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13124             DisplayError(_("Bad FEN position in file"), 0);
13125             return FALSE;
13126         }
13127     } else {
13128         (void) fgets(line, MSG_SIZ, f);
13129         (void) fgets(line, MSG_SIZ, f);
13130
13131         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13132             (void) fgets(line, MSG_SIZ, f);
13133             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13134                 if (*p == ' ')
13135                   continue;
13136                 initial_position[i][j++] = CharToPiece(*p);
13137             }
13138         }
13139
13140         blackPlaysFirst = FALSE;
13141         if (!feof(f)) {
13142             (void) fgets(line, MSG_SIZ, f);
13143             if (strncmp(line, "black", strlen("black"))==0)
13144               blackPlaysFirst = TRUE;
13145         }
13146     }
13147     startedFromSetupPosition = TRUE;
13148
13149     CopyBoard(boards[0], initial_position);
13150     if (blackPlaysFirst) {
13151         currentMove = forwardMostMove = backwardMostMove = 1;
13152         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13153         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13154         CopyBoard(boards[1], initial_position);
13155         DisplayMessage("", _("Black to play"));
13156     } else {
13157         currentMove = forwardMostMove = backwardMostMove = 0;
13158         DisplayMessage("", _("White to play"));
13159     }
13160     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13161     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13162         SendToProgram("force\n", &first);
13163         SendBoard(&first, forwardMostMove);
13164     }
13165     if (appData.debugMode) {
13166 int i, j;
13167   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13168   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13169         fprintf(debugFP, "Load Position\n");
13170     }
13171
13172     if (positionNumber > 1) {
13173       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13174         DisplayTitle(line);
13175     } else {
13176         DisplayTitle(title);
13177     }
13178     gameMode = EditGame;
13179     ModeHighlight();
13180     ResetClocks();
13181     timeRemaining[0][1] = whiteTimeRemaining;
13182     timeRemaining[1][1] = blackTimeRemaining;
13183     DrawPosition(FALSE, boards[currentMove]);
13184
13185     return TRUE;
13186 }
13187
13188
13189 void
13190 CopyPlayerNameIntoFileName (char **dest, char *src)
13191 {
13192     while (*src != NULLCHAR && *src != ',') {
13193         if (*src == ' ') {
13194             *(*dest)++ = '_';
13195             src++;
13196         } else {
13197             *(*dest)++ = *src++;
13198         }
13199     }
13200 }
13201
13202 char *
13203 DefaultFileName (char *ext)
13204 {
13205     static char def[MSG_SIZ];
13206     char *p;
13207
13208     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13209         p = def;
13210         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13211         *p++ = '-';
13212         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13213         *p++ = '.';
13214         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13215     } else {
13216         def[0] = NULLCHAR;
13217     }
13218     return def;
13219 }
13220
13221 /* Save the current game to the given file */
13222 int
13223 SaveGameToFile (char *filename, int append)
13224 {
13225     FILE *f;
13226     char buf[MSG_SIZ];
13227     int result, i, t,tot=0;
13228
13229     if (strcmp(filename, "-") == 0) {
13230         return SaveGame(stdout, 0, NULL);
13231     } else {
13232         for(i=0; i<10; i++) { // upto 10 tries
13233              f = fopen(filename, append ? "a" : "w");
13234              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13235              if(f || errno != 13) break;
13236              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13237              tot += t;
13238         }
13239         if (f == NULL) {
13240             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13241             DisplayError(buf, errno);
13242             return FALSE;
13243         } else {
13244             safeStrCpy(buf, lastMsg, MSG_SIZ);
13245             DisplayMessage(_("Waiting for access to save file"), "");
13246             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13247             DisplayMessage(_("Saving game"), "");
13248             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13249             result = SaveGame(f, 0, NULL);
13250             DisplayMessage(buf, "");
13251             return result;
13252         }
13253     }
13254 }
13255
13256 char *
13257 SavePart (char *str)
13258 {
13259     static char buf[MSG_SIZ];
13260     char *p;
13261
13262     p = strchr(str, ' ');
13263     if (p == NULL) return str;
13264     strncpy(buf, str, p - str);
13265     buf[p - str] = NULLCHAR;
13266     return buf;
13267 }
13268
13269 #define PGN_MAX_LINE 75
13270
13271 #define PGN_SIDE_WHITE  0
13272 #define PGN_SIDE_BLACK  1
13273
13274 static int
13275 FindFirstMoveOutOfBook (int side)
13276 {
13277     int result = -1;
13278
13279     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13280         int index = backwardMostMove;
13281         int has_book_hit = 0;
13282
13283         if( (index % 2) != side ) {
13284             index++;
13285         }
13286
13287         while( index < forwardMostMove ) {
13288             /* Check to see if engine is in book */
13289             int depth = pvInfoList[index].depth;
13290             int score = pvInfoList[index].score;
13291             int in_book = 0;
13292
13293             if( depth <= 2 ) {
13294                 in_book = 1;
13295             }
13296             else if( score == 0 && depth == 63 ) {
13297                 in_book = 1; /* Zappa */
13298             }
13299             else if( score == 2 && depth == 99 ) {
13300                 in_book = 1; /* Abrok */
13301             }
13302
13303             has_book_hit += in_book;
13304
13305             if( ! in_book ) {
13306                 result = index;
13307
13308                 break;
13309             }
13310
13311             index += 2;
13312         }
13313     }
13314
13315     return result;
13316 }
13317
13318 void
13319 GetOutOfBookInfo (char * buf)
13320 {
13321     int oob[2];
13322     int i;
13323     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13324
13325     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13326     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13327
13328     *buf = '\0';
13329
13330     if( oob[0] >= 0 || oob[1] >= 0 ) {
13331         for( i=0; i<2; i++ ) {
13332             int idx = oob[i];
13333
13334             if( idx >= 0 ) {
13335                 if( i > 0 && oob[0] >= 0 ) {
13336                     strcat( buf, "   " );
13337                 }
13338
13339                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13340                 sprintf( buf+strlen(buf), "%s%.2f",
13341                     pvInfoList[idx].score >= 0 ? "+" : "",
13342                     pvInfoList[idx].score / 100.0 );
13343             }
13344         }
13345     }
13346 }
13347
13348 /* Save game in PGN style and close the file */
13349 int
13350 SaveGamePGN (FILE *f)
13351 {
13352     int i, offset, linelen, newblock;
13353 //    char *movetext;
13354     char numtext[32];
13355     int movelen, numlen, blank;
13356     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13357
13358     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13359
13360     PrintPGNTags(f, &gameInfo);
13361
13362     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13363
13364     if (backwardMostMove > 0 || startedFromSetupPosition) {
13365         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13366         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13367         fprintf(f, "\n{--------------\n");
13368         PrintPosition(f, backwardMostMove);
13369         fprintf(f, "--------------}\n");
13370         free(fen);
13371     }
13372     else {
13373         /* [AS] Out of book annotation */
13374         if( appData.saveOutOfBookInfo ) {
13375             char buf[64];
13376
13377             GetOutOfBookInfo( buf );
13378
13379             if( buf[0] != '\0' ) {
13380                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13381             }
13382         }
13383
13384         fprintf(f, "\n");
13385     }
13386
13387     i = backwardMostMove;
13388     linelen = 0;
13389     newblock = TRUE;
13390
13391     while (i < forwardMostMove) {
13392         /* Print comments preceding this move */
13393         if (commentList[i] != NULL) {
13394             if (linelen > 0) fprintf(f, "\n");
13395             fprintf(f, "%s", commentList[i]);
13396             linelen = 0;
13397             newblock = TRUE;
13398         }
13399
13400         /* Format move number */
13401         if ((i % 2) == 0)
13402           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13403         else
13404           if (newblock)
13405             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13406           else
13407             numtext[0] = NULLCHAR;
13408
13409         numlen = strlen(numtext);
13410         newblock = FALSE;
13411
13412         /* Print move number */
13413         blank = linelen > 0 && numlen > 0;
13414         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13415             fprintf(f, "\n");
13416             linelen = 0;
13417             blank = 0;
13418         }
13419         if (blank) {
13420             fprintf(f, " ");
13421             linelen++;
13422         }
13423         fprintf(f, "%s", numtext);
13424         linelen += numlen;
13425
13426         /* Get move */
13427         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13428         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13429
13430         /* Print move */
13431         blank = linelen > 0 && movelen > 0;
13432         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13433             fprintf(f, "\n");
13434             linelen = 0;
13435             blank = 0;
13436         }
13437         if (blank) {
13438             fprintf(f, " ");
13439             linelen++;
13440         }
13441         fprintf(f, "%s", move_buffer);
13442         linelen += movelen;
13443
13444         /* [AS] Add PV info if present */
13445         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13446             /* [HGM] add time */
13447             char buf[MSG_SIZ]; int seconds;
13448
13449             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13450
13451             if( seconds <= 0)
13452               buf[0] = 0;
13453             else
13454               if( seconds < 30 )
13455                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13456               else
13457                 {
13458                   seconds = (seconds + 4)/10; // round to full seconds
13459                   if( seconds < 60 )
13460                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13461                   else
13462                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13463                 }
13464
13465             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13466                       pvInfoList[i].score >= 0 ? "+" : "",
13467                       pvInfoList[i].score / 100.0,
13468                       pvInfoList[i].depth,
13469                       buf );
13470
13471             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13472
13473             /* Print score/depth */
13474             blank = linelen > 0 && movelen > 0;
13475             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13476                 fprintf(f, "\n");
13477                 linelen = 0;
13478                 blank = 0;
13479             }
13480             if (blank) {
13481                 fprintf(f, " ");
13482                 linelen++;
13483             }
13484             fprintf(f, "%s", move_buffer);
13485             linelen += movelen;
13486         }
13487
13488         i++;
13489     }
13490
13491     /* Start a new line */
13492     if (linelen > 0) fprintf(f, "\n");
13493
13494     /* Print comments after last move */
13495     if (commentList[i] != NULL) {
13496         fprintf(f, "%s\n", commentList[i]);
13497     }
13498
13499     /* Print result */
13500     if (gameInfo.resultDetails != NULL &&
13501         gameInfo.resultDetails[0] != NULLCHAR) {
13502         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13503         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13504            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13505             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13506         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13507     } else {
13508         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13509     }
13510
13511     fclose(f);
13512     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13513     return TRUE;
13514 }
13515
13516 /* Save game in old style and close the file */
13517 int
13518 SaveGameOldStyle (FILE *f)
13519 {
13520     int i, offset;
13521     time_t tm;
13522
13523     tm = time((time_t *) NULL);
13524
13525     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13526     PrintOpponents(f);
13527
13528     if (backwardMostMove > 0 || startedFromSetupPosition) {
13529         fprintf(f, "\n[--------------\n");
13530         PrintPosition(f, backwardMostMove);
13531         fprintf(f, "--------------]\n");
13532     } else {
13533         fprintf(f, "\n");
13534     }
13535
13536     i = backwardMostMove;
13537     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13538
13539     while (i < forwardMostMove) {
13540         if (commentList[i] != NULL) {
13541             fprintf(f, "[%s]\n", commentList[i]);
13542         }
13543
13544         if ((i % 2) == 1) {
13545             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13546             i++;
13547         } else {
13548             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13549             i++;
13550             if (commentList[i] != NULL) {
13551                 fprintf(f, "\n");
13552                 continue;
13553             }
13554             if (i >= forwardMostMove) {
13555                 fprintf(f, "\n");
13556                 break;
13557             }
13558             fprintf(f, "%s\n", parseList[i]);
13559             i++;
13560         }
13561     }
13562
13563     if (commentList[i] != NULL) {
13564         fprintf(f, "[%s]\n", commentList[i]);
13565     }
13566
13567     /* This isn't really the old style, but it's close enough */
13568     if (gameInfo.resultDetails != NULL &&
13569         gameInfo.resultDetails[0] != NULLCHAR) {
13570         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13571                 gameInfo.resultDetails);
13572     } else {
13573         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13574     }
13575
13576     fclose(f);
13577     return TRUE;
13578 }
13579
13580 /* Save the current game to open file f and close the file */
13581 int
13582 SaveGame (FILE *f, int dummy, char *dummy2)
13583 {
13584     if (gameMode == EditPosition) EditPositionDone(TRUE);
13585     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13586     if (appData.oldSaveStyle)
13587       return SaveGameOldStyle(f);
13588     else
13589       return SaveGamePGN(f);
13590 }
13591
13592 /* Save the current position to the given file */
13593 int
13594 SavePositionToFile (char *filename)
13595 {
13596     FILE *f;
13597     char buf[MSG_SIZ];
13598
13599     if (strcmp(filename, "-") == 0) {
13600         return SavePosition(stdout, 0, NULL);
13601     } else {
13602         f = fopen(filename, "a");
13603         if (f == NULL) {
13604             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13605             DisplayError(buf, errno);
13606             return FALSE;
13607         } else {
13608             safeStrCpy(buf, lastMsg, MSG_SIZ);
13609             DisplayMessage(_("Waiting for access to save file"), "");
13610             flock(fileno(f), LOCK_EX); // [HGM] lock
13611             DisplayMessage(_("Saving position"), "");
13612             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13613             SavePosition(f, 0, NULL);
13614             DisplayMessage(buf, "");
13615             return TRUE;
13616         }
13617     }
13618 }
13619
13620 /* Save the current position to the given open file and close the file */
13621 int
13622 SavePosition (FILE *f, int dummy, char *dummy2)
13623 {
13624     time_t tm;
13625     char *fen;
13626
13627     if (gameMode == EditPosition) EditPositionDone(TRUE);
13628     if (appData.oldSaveStyle) {
13629         tm = time((time_t *) NULL);
13630
13631         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13632         PrintOpponents(f);
13633         fprintf(f, "[--------------\n");
13634         PrintPosition(f, currentMove);
13635         fprintf(f, "--------------]\n");
13636     } else {
13637         fen = PositionToFEN(currentMove, NULL, 1);
13638         fprintf(f, "%s\n", fen);
13639         free(fen);
13640     }
13641     fclose(f);
13642     return TRUE;
13643 }
13644
13645 void
13646 ReloadCmailMsgEvent (int unregister)
13647 {
13648 #if !WIN32
13649     static char *inFilename = NULL;
13650     static char *outFilename;
13651     int i;
13652     struct stat inbuf, outbuf;
13653     int status;
13654
13655     /* Any registered moves are unregistered if unregister is set, */
13656     /* i.e. invoked by the signal handler */
13657     if (unregister) {
13658         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13659             cmailMoveRegistered[i] = FALSE;
13660             if (cmailCommentList[i] != NULL) {
13661                 free(cmailCommentList[i]);
13662                 cmailCommentList[i] = NULL;
13663             }
13664         }
13665         nCmailMovesRegistered = 0;
13666     }
13667
13668     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13669         cmailResult[i] = CMAIL_NOT_RESULT;
13670     }
13671     nCmailResults = 0;
13672
13673     if (inFilename == NULL) {
13674         /* Because the filenames are static they only get malloced once  */
13675         /* and they never get freed                                      */
13676         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13677         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13678
13679         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13680         sprintf(outFilename, "%s.out", appData.cmailGameName);
13681     }
13682
13683     status = stat(outFilename, &outbuf);
13684     if (status < 0) {
13685         cmailMailedMove = FALSE;
13686     } else {
13687         status = stat(inFilename, &inbuf);
13688         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13689     }
13690
13691     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13692        counts the games, notes how each one terminated, etc.
13693
13694        It would be nice to remove this kludge and instead gather all
13695        the information while building the game list.  (And to keep it
13696        in the game list nodes instead of having a bunch of fixed-size
13697        parallel arrays.)  Note this will require getting each game's
13698        termination from the PGN tags, as the game list builder does
13699        not process the game moves.  --mann
13700        */
13701     cmailMsgLoaded = TRUE;
13702     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13703
13704     /* Load first game in the file or popup game menu */
13705     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13706
13707 #endif /* !WIN32 */
13708     return;
13709 }
13710
13711 int
13712 RegisterMove ()
13713 {
13714     FILE *f;
13715     char string[MSG_SIZ];
13716
13717     if (   cmailMailedMove
13718         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13719         return TRUE;            /* Allow free viewing  */
13720     }
13721
13722     /* Unregister move to ensure that we don't leave RegisterMove        */
13723     /* with the move registered when the conditions for registering no   */
13724     /* longer hold                                                       */
13725     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13726         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13727         nCmailMovesRegistered --;
13728
13729         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13730           {
13731               free(cmailCommentList[lastLoadGameNumber - 1]);
13732               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13733           }
13734     }
13735
13736     if (cmailOldMove == -1) {
13737         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13738         return FALSE;
13739     }
13740
13741     if (currentMove > cmailOldMove + 1) {
13742         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13743         return FALSE;
13744     }
13745
13746     if (currentMove < cmailOldMove) {
13747         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13748         return FALSE;
13749     }
13750
13751     if (forwardMostMove > currentMove) {
13752         /* Silently truncate extra moves */
13753         TruncateGame();
13754     }
13755
13756     if (   (currentMove == cmailOldMove + 1)
13757         || (   (currentMove == cmailOldMove)
13758             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13759                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13760         if (gameInfo.result != GameUnfinished) {
13761             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13762         }
13763
13764         if (commentList[currentMove] != NULL) {
13765             cmailCommentList[lastLoadGameNumber - 1]
13766               = StrSave(commentList[currentMove]);
13767         }
13768         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13769
13770         if (appData.debugMode)
13771           fprintf(debugFP, "Saving %s for game %d\n",
13772                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13773
13774         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13775
13776         f = fopen(string, "w");
13777         if (appData.oldSaveStyle) {
13778             SaveGameOldStyle(f); /* also closes the file */
13779
13780             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13781             f = fopen(string, "w");
13782             SavePosition(f, 0, NULL); /* also closes the file */
13783         } else {
13784             fprintf(f, "{--------------\n");
13785             PrintPosition(f, currentMove);
13786             fprintf(f, "--------------}\n\n");
13787
13788             SaveGame(f, 0, NULL); /* also closes the file*/
13789         }
13790
13791         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13792         nCmailMovesRegistered ++;
13793     } else if (nCmailGames == 1) {
13794         DisplayError(_("You have not made a move yet"), 0);
13795         return FALSE;
13796     }
13797
13798     return TRUE;
13799 }
13800
13801 void
13802 MailMoveEvent ()
13803 {
13804 #if !WIN32
13805     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13806     FILE *commandOutput;
13807     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13808     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13809     int nBuffers;
13810     int i;
13811     int archived;
13812     char *arcDir;
13813
13814     if (! cmailMsgLoaded) {
13815         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13816         return;
13817     }
13818
13819     if (nCmailGames == nCmailResults) {
13820         DisplayError(_("No unfinished games"), 0);
13821         return;
13822     }
13823
13824 #if CMAIL_PROHIBIT_REMAIL
13825     if (cmailMailedMove) {
13826       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);
13827         DisplayError(msg, 0);
13828         return;
13829     }
13830 #endif
13831
13832     if (! (cmailMailedMove || RegisterMove())) return;
13833
13834     if (   cmailMailedMove
13835         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13836       snprintf(string, MSG_SIZ, partCommandString,
13837                appData.debugMode ? " -v" : "", appData.cmailGameName);
13838         commandOutput = popen(string, "r");
13839
13840         if (commandOutput == NULL) {
13841             DisplayError(_("Failed to invoke cmail"), 0);
13842         } else {
13843             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13844                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13845             }
13846             if (nBuffers > 1) {
13847                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13848                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13849                 nBytes = MSG_SIZ - 1;
13850             } else {
13851                 (void) memcpy(msg, buffer, nBytes);
13852             }
13853             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13854
13855             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13856                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13857
13858                 archived = TRUE;
13859                 for (i = 0; i < nCmailGames; i ++) {
13860                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13861                         archived = FALSE;
13862                     }
13863                 }
13864                 if (   archived
13865                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13866                         != NULL)) {
13867                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13868                            arcDir,
13869                            appData.cmailGameName,
13870                            gameInfo.date);
13871                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13872                     cmailMsgLoaded = FALSE;
13873                 }
13874             }
13875
13876             DisplayInformation(msg);
13877             pclose(commandOutput);
13878         }
13879     } else {
13880         if ((*cmailMsg) != '\0') {
13881             DisplayInformation(cmailMsg);
13882         }
13883     }
13884
13885     return;
13886 #endif /* !WIN32 */
13887 }
13888
13889 char *
13890 CmailMsg ()
13891 {
13892 #if WIN32
13893     return NULL;
13894 #else
13895     int  prependComma = 0;
13896     char number[5];
13897     char string[MSG_SIZ];       /* Space for game-list */
13898     int  i;
13899
13900     if (!cmailMsgLoaded) return "";
13901
13902     if (cmailMailedMove) {
13903       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13904     } else {
13905         /* Create a list of games left */
13906       snprintf(string, MSG_SIZ, "[");
13907         for (i = 0; i < nCmailGames; i ++) {
13908             if (! (   cmailMoveRegistered[i]
13909                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13910                 if (prependComma) {
13911                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13912                 } else {
13913                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13914                     prependComma = 1;
13915                 }
13916
13917                 strcat(string, number);
13918             }
13919         }
13920         strcat(string, "]");
13921
13922         if (nCmailMovesRegistered + nCmailResults == 0) {
13923             switch (nCmailGames) {
13924               case 1:
13925                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13926                 break;
13927
13928               case 2:
13929                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13930                 break;
13931
13932               default:
13933                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13934                          nCmailGames);
13935                 break;
13936             }
13937         } else {
13938             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13939               case 1:
13940                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13941                          string);
13942                 break;
13943
13944               case 0:
13945                 if (nCmailResults == nCmailGames) {
13946                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13947                 } else {
13948                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13949                 }
13950                 break;
13951
13952               default:
13953                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13954                          string);
13955             }
13956         }
13957     }
13958     return cmailMsg;
13959 #endif /* WIN32 */
13960 }
13961
13962 void
13963 ResetGameEvent ()
13964 {
13965     if (gameMode == Training)
13966       SetTrainingModeOff();
13967
13968     Reset(TRUE, TRUE);
13969     cmailMsgLoaded = FALSE;
13970     if (appData.icsActive) {
13971       SendToICS(ics_prefix);
13972       SendToICS("refresh\n");
13973     }
13974 }
13975
13976 void
13977 ExitEvent (int status)
13978 {
13979     exiting++;
13980     if (exiting > 2) {
13981       /* Give up on clean exit */
13982       exit(status);
13983     }
13984     if (exiting > 1) {
13985       /* Keep trying for clean exit */
13986       return;
13987     }
13988
13989     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
13990     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13991
13992     if (telnetISR != NULL) {
13993       RemoveInputSource(telnetISR);
13994     }
13995     if (icsPR != NoProc) {
13996       DestroyChildProcess(icsPR, TRUE);
13997     }
13998
13999     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14000     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14001
14002     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14003     /* make sure this other one finishes before killing it!                  */
14004     if(endingGame) { int count = 0;
14005         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14006         while(endingGame && count++ < 10) DoSleep(1);
14007         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14008     }
14009
14010     /* Kill off chess programs */
14011     if (first.pr != NoProc) {
14012         ExitAnalyzeMode();
14013
14014         DoSleep( appData.delayBeforeQuit );
14015         SendToProgram("quit\n", &first);
14016         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14017     }
14018     if (second.pr != NoProc) {
14019         DoSleep( appData.delayBeforeQuit );
14020         SendToProgram("quit\n", &second);
14021         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14022     }
14023     if (first.isr != NULL) {
14024         RemoveInputSource(first.isr);
14025     }
14026     if (second.isr != NULL) {
14027         RemoveInputSource(second.isr);
14028     }
14029
14030     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14031     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14032
14033     ShutDownFrontEnd();
14034     exit(status);
14035 }
14036
14037 void
14038 PauseEngine (ChessProgramState *cps)
14039 {
14040     SendToProgram("pause\n", cps);
14041     cps->pause = 2;
14042 }
14043
14044 void
14045 UnPauseEngine (ChessProgramState *cps)
14046 {
14047     SendToProgram("resume\n", cps);
14048     cps->pause = 1;
14049 }
14050
14051 void
14052 PauseEvent ()
14053 {
14054     if (appData.debugMode)
14055         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14056     if (pausing) {
14057         pausing = FALSE;
14058         ModeHighlight();
14059         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14060             StartClocks();
14061             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14062                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14063                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14064             }
14065             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14066             HandleMachineMove(stashedInputMove, stalledEngine);
14067             stalledEngine = NULL;
14068             return;
14069         }
14070         if (gameMode == MachinePlaysWhite ||
14071             gameMode == TwoMachinesPlay   ||
14072             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14073             if(first.pause)  UnPauseEngine(&first);
14074             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14075             if(second.pause) UnPauseEngine(&second);
14076             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14077             StartClocks();
14078         } else {
14079             DisplayBothClocks();
14080         }
14081         if (gameMode == PlayFromGameFile) {
14082             if (appData.timeDelay >= 0)
14083                 AutoPlayGameLoop();
14084         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14085             Reset(FALSE, TRUE);
14086             SendToICS(ics_prefix);
14087             SendToICS("refresh\n");
14088         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14089             ForwardInner(forwardMostMove);
14090         }
14091         pauseExamInvalid = FALSE;
14092     } else {
14093         switch (gameMode) {
14094           default:
14095             return;
14096           case IcsExamining:
14097             pauseExamForwardMostMove = forwardMostMove;
14098             pauseExamInvalid = FALSE;
14099             /* fall through */
14100           case IcsObserving:
14101           case IcsPlayingWhite:
14102           case IcsPlayingBlack:
14103             pausing = TRUE;
14104             ModeHighlight();
14105             return;
14106           case PlayFromGameFile:
14107             (void) StopLoadGameTimer();
14108             pausing = TRUE;
14109             ModeHighlight();
14110             break;
14111           case BeginningOfGame:
14112             if (appData.icsActive) return;
14113             /* else fall through */
14114           case MachinePlaysWhite:
14115           case MachinePlaysBlack:
14116           case TwoMachinesPlay:
14117             if (forwardMostMove == 0)
14118               return;           /* don't pause if no one has moved */
14119             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14120                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14121                 if(onMove->pause) {           // thinking engine can be paused
14122                     PauseEngine(onMove);      // do it
14123                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14124                         PauseEngine(onMove->other);
14125                     else
14126                         SendToProgram("easy\n", onMove->other);
14127                     StopClocks();
14128                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14129             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14130                 if(first.pause) {
14131                     PauseEngine(&first);
14132                     StopClocks();
14133                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14134             } else { // human on move, pause pondering by either method
14135                 if(first.pause)
14136                     PauseEngine(&first);
14137                 else if(appData.ponderNextMove)
14138                     SendToProgram("easy\n", &first);
14139                 StopClocks();
14140             }
14141             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14142           case AnalyzeMode:
14143             pausing = TRUE;
14144             ModeHighlight();
14145             break;
14146         }
14147     }
14148 }
14149
14150 void
14151 EditCommentEvent ()
14152 {
14153     char title[MSG_SIZ];
14154
14155     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14156       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14157     } else {
14158       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14159                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14160                parseList[currentMove - 1]);
14161     }
14162
14163     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14164 }
14165
14166
14167 void
14168 EditTagsEvent ()
14169 {
14170     char *tags = PGNTags(&gameInfo);
14171     bookUp = FALSE;
14172     EditTagsPopUp(tags, NULL);
14173     free(tags);
14174 }
14175
14176 void
14177 ToggleSecond ()
14178 {
14179   if(second.analyzing) {
14180     SendToProgram("exit\n", &second);
14181     second.analyzing = FALSE;
14182   } else {
14183     if (second.pr == NoProc) StartChessProgram(&second);
14184     InitChessProgram(&second, FALSE);
14185     FeedMovesToProgram(&second, currentMove);
14186
14187     SendToProgram("analyze\n", &second);
14188     second.analyzing = TRUE;
14189   }
14190 }
14191
14192 /* Toggle ShowThinking */
14193 void
14194 ToggleShowThinking()
14195 {
14196   appData.showThinking = !appData.showThinking;
14197   ShowThinkingEvent();
14198 }
14199
14200 int
14201 AnalyzeModeEvent ()
14202 {
14203     char buf[MSG_SIZ];
14204
14205     if (!first.analysisSupport) {
14206       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14207       DisplayError(buf, 0);
14208       return 0;
14209     }
14210     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14211     if (appData.icsActive) {
14212         if (gameMode != IcsObserving) {
14213           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14214             DisplayError(buf, 0);
14215             /* secure check */
14216             if (appData.icsEngineAnalyze) {
14217                 if (appData.debugMode)
14218                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14219                 ExitAnalyzeMode();
14220                 ModeHighlight();
14221             }
14222             return 0;
14223         }
14224         /* if enable, user wants to disable icsEngineAnalyze */
14225         if (appData.icsEngineAnalyze) {
14226                 ExitAnalyzeMode();
14227                 ModeHighlight();
14228                 return 0;
14229         }
14230         appData.icsEngineAnalyze = TRUE;
14231         if (appData.debugMode)
14232             fprintf(debugFP, "ICS engine analyze starting... \n");
14233     }
14234
14235     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14236     if (appData.noChessProgram || gameMode == AnalyzeMode)
14237       return 0;
14238
14239     if (gameMode != AnalyzeFile) {
14240         if (!appData.icsEngineAnalyze) {
14241                EditGameEvent();
14242                if (gameMode != EditGame) return 0;
14243         }
14244         if (!appData.showThinking) ToggleShowThinking();
14245         ResurrectChessProgram();
14246         SendToProgram("analyze\n", &first);
14247         first.analyzing = TRUE;
14248         /*first.maybeThinking = TRUE;*/
14249         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14250         EngineOutputPopUp();
14251     }
14252     if (!appData.icsEngineAnalyze) {
14253         gameMode = AnalyzeMode;
14254         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14255     }
14256     pausing = FALSE;
14257     ModeHighlight();
14258     SetGameInfo();
14259
14260     StartAnalysisClock();
14261     GetTimeMark(&lastNodeCountTime);
14262     lastNodeCount = 0;
14263     return 1;
14264 }
14265
14266 void
14267 AnalyzeFileEvent ()
14268 {
14269     if (appData.noChessProgram || gameMode == AnalyzeFile)
14270       return;
14271
14272     if (!first.analysisSupport) {
14273       char buf[MSG_SIZ];
14274       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14275       DisplayError(buf, 0);
14276       return;
14277     }
14278
14279     if (gameMode != AnalyzeMode) {
14280         keepInfo = 1; // mere annotating should not alter PGN tags
14281         EditGameEvent();
14282         keepInfo = 0;
14283         if (gameMode != EditGame) return;
14284         if (!appData.showThinking) ToggleShowThinking();
14285         ResurrectChessProgram();
14286         SendToProgram("analyze\n", &first);
14287         first.analyzing = TRUE;
14288         /*first.maybeThinking = TRUE;*/
14289         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14290         EngineOutputPopUp();
14291     }
14292     gameMode = AnalyzeFile;
14293     pausing = FALSE;
14294     ModeHighlight();
14295
14296     StartAnalysisClock();
14297     GetTimeMark(&lastNodeCountTime);
14298     lastNodeCount = 0;
14299     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14300     AnalysisPeriodicEvent(1);
14301 }
14302
14303 void
14304 MachineWhiteEvent ()
14305 {
14306     char buf[MSG_SIZ];
14307     char *bookHit = NULL;
14308
14309     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14310       return;
14311
14312
14313     if (gameMode == PlayFromGameFile ||
14314         gameMode == TwoMachinesPlay  ||
14315         gameMode == Training         ||
14316         gameMode == AnalyzeMode      ||
14317         gameMode == EndOfGame)
14318         EditGameEvent();
14319
14320     if (gameMode == EditPosition)
14321         EditPositionDone(TRUE);
14322
14323     if (!WhiteOnMove(currentMove)) {
14324         DisplayError(_("It is not White's turn"), 0);
14325         return;
14326     }
14327
14328     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14329       ExitAnalyzeMode();
14330
14331     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14332         gameMode == AnalyzeFile)
14333         TruncateGame();
14334
14335     ResurrectChessProgram();    /* in case it isn't running */
14336     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14337         gameMode = MachinePlaysWhite;
14338         ResetClocks();
14339     } else
14340     gameMode = MachinePlaysWhite;
14341     pausing = FALSE;
14342     ModeHighlight();
14343     SetGameInfo();
14344     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14345     DisplayTitle(buf);
14346     if (first.sendName) {
14347       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14348       SendToProgram(buf, &first);
14349     }
14350     if (first.sendTime) {
14351       if (first.useColors) {
14352         SendToProgram("black\n", &first); /*gnu kludge*/
14353       }
14354       SendTimeRemaining(&first, TRUE);
14355     }
14356     if (first.useColors) {
14357       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14358     }
14359     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14360     SetMachineThinkingEnables();
14361     first.maybeThinking = TRUE;
14362     StartClocks();
14363     firstMove = FALSE;
14364
14365     if (appData.autoFlipView && !flipView) {
14366       flipView = !flipView;
14367       DrawPosition(FALSE, NULL);
14368       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14369     }
14370
14371     if(bookHit) { // [HGM] book: simulate book reply
14372         static char bookMove[MSG_SIZ]; // a bit generous?
14373
14374         programStats.nodes = programStats.depth = programStats.time =
14375         programStats.score = programStats.got_only_move = 0;
14376         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14377
14378         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14379         strcat(bookMove, bookHit);
14380         HandleMachineMove(bookMove, &first);
14381     }
14382 }
14383
14384 void
14385 MachineBlackEvent ()
14386 {
14387   char buf[MSG_SIZ];
14388   char *bookHit = NULL;
14389
14390     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14391         return;
14392
14393
14394     if (gameMode == PlayFromGameFile ||
14395         gameMode == TwoMachinesPlay  ||
14396         gameMode == Training         ||
14397         gameMode == AnalyzeMode      ||
14398         gameMode == EndOfGame)
14399         EditGameEvent();
14400
14401     if (gameMode == EditPosition)
14402         EditPositionDone(TRUE);
14403
14404     if (WhiteOnMove(currentMove)) {
14405         DisplayError(_("It is not Black's turn"), 0);
14406         return;
14407     }
14408
14409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14410       ExitAnalyzeMode();
14411
14412     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14413         gameMode == AnalyzeFile)
14414         TruncateGame();
14415
14416     ResurrectChessProgram();    /* in case it isn't running */
14417     gameMode = MachinePlaysBlack;
14418     pausing = FALSE;
14419     ModeHighlight();
14420     SetGameInfo();
14421     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14422     DisplayTitle(buf);
14423     if (first.sendName) {
14424       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14425       SendToProgram(buf, &first);
14426     }
14427     if (first.sendTime) {
14428       if (first.useColors) {
14429         SendToProgram("white\n", &first); /*gnu kludge*/
14430       }
14431       SendTimeRemaining(&first, FALSE);
14432     }
14433     if (first.useColors) {
14434       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14435     }
14436     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14437     SetMachineThinkingEnables();
14438     first.maybeThinking = TRUE;
14439     StartClocks();
14440
14441     if (appData.autoFlipView && flipView) {
14442       flipView = !flipView;
14443       DrawPosition(FALSE, NULL);
14444       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14445     }
14446     if(bookHit) { // [HGM] book: simulate book reply
14447         static char bookMove[MSG_SIZ]; // a bit generous?
14448
14449         programStats.nodes = programStats.depth = programStats.time =
14450         programStats.score = programStats.got_only_move = 0;
14451         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14452
14453         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14454         strcat(bookMove, bookHit);
14455         HandleMachineMove(bookMove, &first);
14456     }
14457 }
14458
14459
14460 void
14461 DisplayTwoMachinesTitle ()
14462 {
14463     char buf[MSG_SIZ];
14464     if (appData.matchGames > 0) {
14465         if(appData.tourneyFile[0]) {
14466           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14467                    gameInfo.white, _("vs."), gameInfo.black,
14468                    nextGame+1, appData.matchGames+1,
14469                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14470         } else
14471         if (first.twoMachinesColor[0] == 'w') {
14472           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14473                    gameInfo.white, _("vs."),  gameInfo.black,
14474                    first.matchWins, second.matchWins,
14475                    matchGame - 1 - (first.matchWins + second.matchWins));
14476         } else {
14477           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14478                    gameInfo.white, _("vs."), gameInfo.black,
14479                    second.matchWins, first.matchWins,
14480                    matchGame - 1 - (first.matchWins + second.matchWins));
14481         }
14482     } else {
14483       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14484     }
14485     DisplayTitle(buf);
14486 }
14487
14488 void
14489 SettingsMenuIfReady ()
14490 {
14491   if (second.lastPing != second.lastPong) {
14492     DisplayMessage("", _("Waiting for second chess program"));
14493     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14494     return;
14495   }
14496   ThawUI();
14497   DisplayMessage("", "");
14498   SettingsPopUp(&second);
14499 }
14500
14501 int
14502 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14503 {
14504     char buf[MSG_SIZ];
14505     if (cps->pr == NoProc) {
14506         StartChessProgram(cps);
14507         if (cps->protocolVersion == 1) {
14508           retry();
14509           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14510         } else {
14511           /* kludge: allow timeout for initial "feature" command */
14512           if(retry != TwoMachinesEventIfReady) FreezeUI();
14513           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14514           DisplayMessage("", buf);
14515           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14516         }
14517         return 1;
14518     }
14519     return 0;
14520 }
14521
14522 void
14523 TwoMachinesEvent P((void))
14524 {
14525     int i;
14526     char buf[MSG_SIZ];
14527     ChessProgramState *onmove;
14528     char *bookHit = NULL;
14529     static int stalling = 0;
14530     TimeMark now;
14531     long wait;
14532
14533     if (appData.noChessProgram) return;
14534
14535     switch (gameMode) {
14536       case TwoMachinesPlay:
14537         return;
14538       case MachinePlaysWhite:
14539       case MachinePlaysBlack:
14540         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14541             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14542             return;
14543         }
14544         /* fall through */
14545       case BeginningOfGame:
14546       case PlayFromGameFile:
14547       case EndOfGame:
14548         EditGameEvent();
14549         if (gameMode != EditGame) return;
14550         break;
14551       case EditPosition:
14552         EditPositionDone(TRUE);
14553         break;
14554       case AnalyzeMode:
14555       case AnalyzeFile:
14556         ExitAnalyzeMode();
14557         break;
14558       case EditGame:
14559       default:
14560         break;
14561     }
14562
14563 //    forwardMostMove = currentMove;
14564     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14565     startingEngine = TRUE;
14566
14567     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14568
14569     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14570     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14571       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14572       return;
14573     }
14574     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14575
14576     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14577                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14578         startingEngine = FALSE;
14579         DisplayError("second engine does not play this", 0);
14580         return;
14581     }
14582
14583     if(!stalling) {
14584       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14585       SendToProgram("force\n", &second);
14586       stalling = 1;
14587       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14588       return;
14589     }
14590     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14591     if(appData.matchPause>10000 || appData.matchPause<10)
14592                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14593     wait = SubtractTimeMarks(&now, &pauseStart);
14594     if(wait < appData.matchPause) {
14595         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14596         return;
14597     }
14598     // we are now committed to starting the game
14599     stalling = 0;
14600     DisplayMessage("", "");
14601     if (startedFromSetupPosition) {
14602         SendBoard(&second, backwardMostMove);
14603     if (appData.debugMode) {
14604         fprintf(debugFP, "Two Machines\n");
14605     }
14606     }
14607     for (i = backwardMostMove; i < forwardMostMove; i++) {
14608         SendMoveToProgram(i, &second);
14609     }
14610
14611     gameMode = TwoMachinesPlay;
14612     pausing = startingEngine = FALSE;
14613     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14614     SetGameInfo();
14615     DisplayTwoMachinesTitle();
14616     firstMove = TRUE;
14617     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14618         onmove = &first;
14619     } else {
14620         onmove = &second;
14621     }
14622     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14623     SendToProgram(first.computerString, &first);
14624     if (first.sendName) {
14625       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14626       SendToProgram(buf, &first);
14627     }
14628     SendToProgram(second.computerString, &second);
14629     if (second.sendName) {
14630       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14631       SendToProgram(buf, &second);
14632     }
14633
14634     ResetClocks();
14635     if (!first.sendTime || !second.sendTime) {
14636         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14637         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14638     }
14639     if (onmove->sendTime) {
14640       if (onmove->useColors) {
14641         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14642       }
14643       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14644     }
14645     if (onmove->useColors) {
14646       SendToProgram(onmove->twoMachinesColor, onmove);
14647     }
14648     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14649 //    SendToProgram("go\n", onmove);
14650     onmove->maybeThinking = TRUE;
14651     SetMachineThinkingEnables();
14652
14653     StartClocks();
14654
14655     if(bookHit) { // [HGM] book: simulate book reply
14656         static char bookMove[MSG_SIZ]; // a bit generous?
14657
14658         programStats.nodes = programStats.depth = programStats.time =
14659         programStats.score = programStats.got_only_move = 0;
14660         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14661
14662         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14663         strcat(bookMove, bookHit);
14664         savedMessage = bookMove; // args for deferred call
14665         savedState = onmove;
14666         ScheduleDelayedEvent(DeferredBookMove, 1);
14667     }
14668 }
14669
14670 void
14671 TrainingEvent ()
14672 {
14673     if (gameMode == Training) {
14674       SetTrainingModeOff();
14675       gameMode = PlayFromGameFile;
14676       DisplayMessage("", _("Training mode off"));
14677     } else {
14678       gameMode = Training;
14679       animateTraining = appData.animate;
14680
14681       /* make sure we are not already at the end of the game */
14682       if (currentMove < forwardMostMove) {
14683         SetTrainingModeOn();
14684         DisplayMessage("", _("Training mode on"));
14685       } else {
14686         gameMode = PlayFromGameFile;
14687         DisplayError(_("Already at end of game"), 0);
14688       }
14689     }
14690     ModeHighlight();
14691 }
14692
14693 void
14694 IcsClientEvent ()
14695 {
14696     if (!appData.icsActive) return;
14697     switch (gameMode) {
14698       case IcsPlayingWhite:
14699       case IcsPlayingBlack:
14700       case IcsObserving:
14701       case IcsIdle:
14702       case BeginningOfGame:
14703       case IcsExamining:
14704         return;
14705
14706       case EditGame:
14707         break;
14708
14709       case EditPosition:
14710         EditPositionDone(TRUE);
14711         break;
14712
14713       case AnalyzeMode:
14714       case AnalyzeFile:
14715         ExitAnalyzeMode();
14716         break;
14717
14718       default:
14719         EditGameEvent();
14720         break;
14721     }
14722
14723     gameMode = IcsIdle;
14724     ModeHighlight();
14725     return;
14726 }
14727
14728 void
14729 EditGameEvent ()
14730 {
14731     int i;
14732
14733     switch (gameMode) {
14734       case Training:
14735         SetTrainingModeOff();
14736         break;
14737       case MachinePlaysWhite:
14738       case MachinePlaysBlack:
14739       case BeginningOfGame:
14740         SendToProgram("force\n", &first);
14741         SetUserThinkingEnables();
14742         break;
14743       case PlayFromGameFile:
14744         (void) StopLoadGameTimer();
14745         if (gameFileFP != NULL) {
14746             gameFileFP = NULL;
14747         }
14748         break;
14749       case EditPosition:
14750         EditPositionDone(TRUE);
14751         break;
14752       case AnalyzeMode:
14753       case AnalyzeFile:
14754         ExitAnalyzeMode();
14755         SendToProgram("force\n", &first);
14756         break;
14757       case TwoMachinesPlay:
14758         GameEnds(EndOfFile, NULL, GE_PLAYER);
14759         ResurrectChessProgram();
14760         SetUserThinkingEnables();
14761         break;
14762       case EndOfGame:
14763         ResurrectChessProgram();
14764         break;
14765       case IcsPlayingBlack:
14766       case IcsPlayingWhite:
14767         DisplayError(_("Warning: You are still playing a game"), 0);
14768         break;
14769       case IcsObserving:
14770         DisplayError(_("Warning: You are still observing a game"), 0);
14771         break;
14772       case IcsExamining:
14773         DisplayError(_("Warning: You are still examining a game"), 0);
14774         break;
14775       case IcsIdle:
14776         break;
14777       case EditGame:
14778       default:
14779         return;
14780     }
14781
14782     pausing = FALSE;
14783     StopClocks();
14784     first.offeredDraw = second.offeredDraw = 0;
14785
14786     if (gameMode == PlayFromGameFile) {
14787         whiteTimeRemaining = timeRemaining[0][currentMove];
14788         blackTimeRemaining = timeRemaining[1][currentMove];
14789         DisplayTitle("");
14790     }
14791
14792     if (gameMode == MachinePlaysWhite ||
14793         gameMode == MachinePlaysBlack ||
14794         gameMode == TwoMachinesPlay ||
14795         gameMode == EndOfGame) {
14796         i = forwardMostMove;
14797         while (i > currentMove) {
14798             SendToProgram("undo\n", &first);
14799             i--;
14800         }
14801         if(!adjustedClock) {
14802         whiteTimeRemaining = timeRemaining[0][currentMove];
14803         blackTimeRemaining = timeRemaining[1][currentMove];
14804         DisplayBothClocks();
14805         }
14806         if (whiteFlag || blackFlag) {
14807             whiteFlag = blackFlag = 0;
14808         }
14809         DisplayTitle("");
14810     }
14811
14812     gameMode = EditGame;
14813     ModeHighlight();
14814     SetGameInfo();
14815 }
14816
14817
14818 void
14819 EditPositionEvent ()
14820 {
14821     if (gameMode == EditPosition) {
14822         EditGameEvent();
14823         return;
14824     }
14825
14826     EditGameEvent();
14827     if (gameMode != EditGame) return;
14828
14829     gameMode = EditPosition;
14830     ModeHighlight();
14831     SetGameInfo();
14832     if (currentMove > 0)
14833       CopyBoard(boards[0], boards[currentMove]);
14834
14835     blackPlaysFirst = !WhiteOnMove(currentMove);
14836     ResetClocks();
14837     currentMove = forwardMostMove = backwardMostMove = 0;
14838     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14839     DisplayMove(-1);
14840     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14841 }
14842
14843 void
14844 ExitAnalyzeMode ()
14845 {
14846     /* [DM] icsEngineAnalyze - possible call from other functions */
14847     if (appData.icsEngineAnalyze) {
14848         appData.icsEngineAnalyze = FALSE;
14849
14850         DisplayMessage("",_("Close ICS engine analyze..."));
14851     }
14852     if (first.analysisSupport && first.analyzing) {
14853       SendToBoth("exit\n");
14854       first.analyzing = second.analyzing = FALSE;
14855     }
14856     thinkOutput[0] = NULLCHAR;
14857 }
14858
14859 void
14860 EditPositionDone (Boolean fakeRights)
14861 {
14862     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14863
14864     startedFromSetupPosition = TRUE;
14865     InitChessProgram(&first, FALSE);
14866     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14867       boards[0][EP_STATUS] = EP_NONE;
14868       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14869       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14870         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14871         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14872       } else boards[0][CASTLING][2] = NoRights;
14873       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14874         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14875         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14876       } else boards[0][CASTLING][5] = NoRights;
14877       if(gameInfo.variant == VariantSChess) {
14878         int i;
14879         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14880           boards[0][VIRGIN][i] = 0;
14881           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14882           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14883         }
14884       }
14885     }
14886     SendToProgram("force\n", &first);
14887     if (blackPlaysFirst) {
14888         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14889         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14890         currentMove = forwardMostMove = backwardMostMove = 1;
14891         CopyBoard(boards[1], boards[0]);
14892     } else {
14893         currentMove = forwardMostMove = backwardMostMove = 0;
14894     }
14895     SendBoard(&first, forwardMostMove);
14896     if (appData.debugMode) {
14897         fprintf(debugFP, "EditPosDone\n");
14898     }
14899     DisplayTitle("");
14900     DisplayMessage("", "");
14901     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14902     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14903     gameMode = EditGame;
14904     ModeHighlight();
14905     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14906     ClearHighlights(); /* [AS] */
14907 }
14908
14909 /* Pause for `ms' milliseconds */
14910 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14911 void
14912 TimeDelay (long ms)
14913 {
14914     TimeMark m1, m2;
14915
14916     GetTimeMark(&m1);
14917     do {
14918         GetTimeMark(&m2);
14919     } while (SubtractTimeMarks(&m2, &m1) < ms);
14920 }
14921
14922 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14923 void
14924 SendMultiLineToICS (char *buf)
14925 {
14926     char temp[MSG_SIZ+1], *p;
14927     int len;
14928
14929     len = strlen(buf);
14930     if (len > MSG_SIZ)
14931       len = MSG_SIZ;
14932
14933     strncpy(temp, buf, len);
14934     temp[len] = 0;
14935
14936     p = temp;
14937     while (*p) {
14938         if (*p == '\n' || *p == '\r')
14939           *p = ' ';
14940         ++p;
14941     }
14942
14943     strcat(temp, "\n");
14944     SendToICS(temp);
14945     SendToPlayer(temp, strlen(temp));
14946 }
14947
14948 void
14949 SetWhiteToPlayEvent ()
14950 {
14951     if (gameMode == EditPosition) {
14952         blackPlaysFirst = FALSE;
14953         DisplayBothClocks();    /* works because currentMove is 0 */
14954     } else if (gameMode == IcsExamining) {
14955         SendToICS(ics_prefix);
14956         SendToICS("tomove white\n");
14957     }
14958 }
14959
14960 void
14961 SetBlackToPlayEvent ()
14962 {
14963     if (gameMode == EditPosition) {
14964         blackPlaysFirst = TRUE;
14965         currentMove = 1;        /* kludge */
14966         DisplayBothClocks();
14967         currentMove = 0;
14968     } else if (gameMode == IcsExamining) {
14969         SendToICS(ics_prefix);
14970         SendToICS("tomove black\n");
14971     }
14972 }
14973
14974 void
14975 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14976 {
14977     char buf[MSG_SIZ];
14978     ChessSquare piece = boards[0][y][x];
14979     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14980     static int lastVariant;
14981
14982     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14983
14984     switch (selection) {
14985       case ClearBoard:
14986         CopyBoard(currentBoard, boards[0]);
14987         CopyBoard(menuBoard, initialPosition);
14988         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14989             SendToICS(ics_prefix);
14990             SendToICS("bsetup clear\n");
14991         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14992             SendToICS(ics_prefix);
14993             SendToICS("clearboard\n");
14994         } else {
14995             int nonEmpty = 0;
14996             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14997                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14998                 for (y = 0; y < BOARD_HEIGHT; y++) {
14999                     if (gameMode == IcsExamining) {
15000                         if (boards[currentMove][y][x] != EmptySquare) {
15001                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15002                                     AAA + x, ONE + y);
15003                             SendToICS(buf);
15004                         }
15005                     } else {
15006                         if(boards[0][y][x] != p) nonEmpty++;
15007                         boards[0][y][x] = p;
15008                     }
15009                 }
15010                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
15011             }
15012             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15013                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15014                     ChessSquare p = menuBoard[0][x];
15015                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
15016                     p = menuBoard[BOARD_HEIGHT-1][x];
15017                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
15018                 }
15019                 DisplayMessage("Clicking clock again restores position", "");
15020                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15021                 if(!nonEmpty) { // asked to clear an empty board
15022                     CopyBoard(boards[0], menuBoard);
15023                 } else
15024                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15025                     CopyBoard(boards[0], initialPosition);
15026                 } else
15027                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15028                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15029                     CopyBoard(boards[0], erasedBoard);
15030                 } else
15031                     CopyBoard(erasedBoard, currentBoard);
15032
15033             }
15034         }
15035         if (gameMode == EditPosition) {
15036             DrawPosition(FALSE, boards[0]);
15037         }
15038         break;
15039
15040       case WhitePlay:
15041         SetWhiteToPlayEvent();
15042         break;
15043
15044       case BlackPlay:
15045         SetBlackToPlayEvent();
15046         break;
15047
15048       case EmptySquare:
15049         if (gameMode == IcsExamining) {
15050             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15051             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15052             SendToICS(buf);
15053         } else {
15054             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15055                 if(x == BOARD_LEFT-2) {
15056                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15057                     boards[0][y][1] = 0;
15058                 } else
15059                 if(x == BOARD_RGHT+1) {
15060                     if(y >= gameInfo.holdingsSize) break;
15061                     boards[0][y][BOARD_WIDTH-2] = 0;
15062                 } else break;
15063             }
15064             boards[0][y][x] = EmptySquare;
15065             DrawPosition(FALSE, boards[0]);
15066         }
15067         break;
15068
15069       case PromotePiece:
15070         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15071            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15072             selection = (ChessSquare) (PROMOTED piece);
15073         } else if(piece == EmptySquare) selection = WhiteSilver;
15074         else selection = (ChessSquare)((int)piece - 1);
15075         goto defaultlabel;
15076
15077       case DemotePiece:
15078         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15079            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15080             selection = (ChessSquare) (DEMOTED piece);
15081         } else if(piece == EmptySquare) selection = BlackSilver;
15082         else selection = (ChessSquare)((int)piece + 1);
15083         goto defaultlabel;
15084
15085       case WhiteQueen:
15086       case BlackQueen:
15087         if(gameInfo.variant == VariantShatranj ||
15088            gameInfo.variant == VariantXiangqi  ||
15089            gameInfo.variant == VariantCourier  ||
15090            gameInfo.variant == VariantASEAN    ||
15091            gameInfo.variant == VariantMakruk     )
15092             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15093         goto defaultlabel;
15094
15095       case WhiteKing:
15096       case BlackKing:
15097         if(gameInfo.variant == VariantXiangqi)
15098             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15099         if(gameInfo.variant == VariantKnightmate)
15100             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15101       default:
15102         defaultlabel:
15103         if (gameMode == IcsExamining) {
15104             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15105             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15106                      PieceToChar(selection), AAA + x, ONE + y);
15107             SendToICS(buf);
15108         } else {
15109             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15110                 int n;
15111                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15112                     n = PieceToNumber(selection - BlackPawn);
15113                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15114                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15115                     boards[0][BOARD_HEIGHT-1-n][1]++;
15116                 } else
15117                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15118                     n = PieceToNumber(selection);
15119                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15120                     boards[0][n][BOARD_WIDTH-1] = selection;
15121                     boards[0][n][BOARD_WIDTH-2]++;
15122                 }
15123             } else
15124             boards[0][y][x] = selection;
15125             DrawPosition(TRUE, boards[0]);
15126             ClearHighlights();
15127             fromX = fromY = -1;
15128         }
15129         break;
15130     }
15131 }
15132
15133
15134 void
15135 DropMenuEvent (ChessSquare selection, int x, int y)
15136 {
15137     ChessMove moveType;
15138
15139     switch (gameMode) {
15140       case IcsPlayingWhite:
15141       case MachinePlaysBlack:
15142         if (!WhiteOnMove(currentMove)) {
15143             DisplayMoveError(_("It is Black's turn"));
15144             return;
15145         }
15146         moveType = WhiteDrop;
15147         break;
15148       case IcsPlayingBlack:
15149       case MachinePlaysWhite:
15150         if (WhiteOnMove(currentMove)) {
15151             DisplayMoveError(_("It is White's turn"));
15152             return;
15153         }
15154         moveType = BlackDrop;
15155         break;
15156       case EditGame:
15157         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15158         break;
15159       default:
15160         return;
15161     }
15162
15163     if (moveType == BlackDrop && selection < BlackPawn) {
15164       selection = (ChessSquare) ((int) selection
15165                                  + (int) BlackPawn - (int) WhitePawn);
15166     }
15167     if (boards[currentMove][y][x] != EmptySquare) {
15168         DisplayMoveError(_("That square is occupied"));
15169         return;
15170     }
15171
15172     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15173 }
15174
15175 void
15176 AcceptEvent ()
15177 {
15178     /* Accept a pending offer of any kind from opponent */
15179
15180     if (appData.icsActive) {
15181         SendToICS(ics_prefix);
15182         SendToICS("accept\n");
15183     } else if (cmailMsgLoaded) {
15184         if (currentMove == cmailOldMove &&
15185             commentList[cmailOldMove] != NULL &&
15186             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15187                    "Black offers a draw" : "White offers a draw")) {
15188             TruncateGame();
15189             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15190             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15191         } else {
15192             DisplayError(_("There is no pending offer on this move"), 0);
15193             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15194         }
15195     } else {
15196         /* Not used for offers from chess program */
15197     }
15198 }
15199
15200 void
15201 DeclineEvent ()
15202 {
15203     /* Decline a pending offer of any kind from opponent */
15204
15205     if (appData.icsActive) {
15206         SendToICS(ics_prefix);
15207         SendToICS("decline\n");
15208     } else if (cmailMsgLoaded) {
15209         if (currentMove == cmailOldMove &&
15210             commentList[cmailOldMove] != NULL &&
15211             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15212                    "Black offers a draw" : "White offers a draw")) {
15213 #ifdef NOTDEF
15214             AppendComment(cmailOldMove, "Draw declined", TRUE);
15215             DisplayComment(cmailOldMove - 1, "Draw declined");
15216 #endif /*NOTDEF*/
15217         } else {
15218             DisplayError(_("There is no pending offer on this move"), 0);
15219         }
15220     } else {
15221         /* Not used for offers from chess program */
15222     }
15223 }
15224
15225 void
15226 RematchEvent ()
15227 {
15228     /* Issue ICS rematch command */
15229     if (appData.icsActive) {
15230         SendToICS(ics_prefix);
15231         SendToICS("rematch\n");
15232     }
15233 }
15234
15235 void
15236 CallFlagEvent ()
15237 {
15238     /* Call your opponent's flag (claim a win on time) */
15239     if (appData.icsActive) {
15240         SendToICS(ics_prefix);
15241         SendToICS("flag\n");
15242     } else {
15243         switch (gameMode) {
15244           default:
15245             return;
15246           case MachinePlaysWhite:
15247             if (whiteFlag) {
15248                 if (blackFlag)
15249                   GameEnds(GameIsDrawn, "Both players ran out of time",
15250                            GE_PLAYER);
15251                 else
15252                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15253             } else {
15254                 DisplayError(_("Your opponent is not out of time"), 0);
15255             }
15256             break;
15257           case MachinePlaysBlack:
15258             if (blackFlag) {
15259                 if (whiteFlag)
15260                   GameEnds(GameIsDrawn, "Both players ran out of time",
15261                            GE_PLAYER);
15262                 else
15263                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15264             } else {
15265                 DisplayError(_("Your opponent is not out of time"), 0);
15266             }
15267             break;
15268         }
15269     }
15270 }
15271
15272 void
15273 ClockClick (int which)
15274 {       // [HGM] code moved to back-end from winboard.c
15275         if(which) { // black clock
15276           if (gameMode == EditPosition || gameMode == IcsExamining) {
15277             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15278             SetBlackToPlayEvent();
15279           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15280           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15281           } else if (shiftKey) {
15282             AdjustClock(which, -1);
15283           } else if (gameMode == IcsPlayingWhite ||
15284                      gameMode == MachinePlaysBlack) {
15285             CallFlagEvent();
15286           }
15287         } else { // white clock
15288           if (gameMode == EditPosition || gameMode == IcsExamining) {
15289             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15290             SetWhiteToPlayEvent();
15291           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15292           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15293           } else if (shiftKey) {
15294             AdjustClock(which, -1);
15295           } else if (gameMode == IcsPlayingBlack ||
15296                    gameMode == MachinePlaysWhite) {
15297             CallFlagEvent();
15298           }
15299         }
15300 }
15301
15302 void
15303 DrawEvent ()
15304 {
15305     /* Offer draw or accept pending draw offer from opponent */
15306
15307     if (appData.icsActive) {
15308         /* Note: tournament rules require draw offers to be
15309            made after you make your move but before you punch
15310            your clock.  Currently ICS doesn't let you do that;
15311            instead, you immediately punch your clock after making
15312            a move, but you can offer a draw at any time. */
15313
15314         SendToICS(ics_prefix);
15315         SendToICS("draw\n");
15316         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15317     } else if (cmailMsgLoaded) {
15318         if (currentMove == cmailOldMove &&
15319             commentList[cmailOldMove] != NULL &&
15320             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15321                    "Black offers a draw" : "White offers a draw")) {
15322             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15323             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15324         } else if (currentMove == cmailOldMove + 1) {
15325             char *offer = WhiteOnMove(cmailOldMove) ?
15326               "White offers a draw" : "Black offers a draw";
15327             AppendComment(currentMove, offer, TRUE);
15328             DisplayComment(currentMove - 1, offer);
15329             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15330         } else {
15331             DisplayError(_("You must make your move before offering a draw"), 0);
15332             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15333         }
15334     } else if (first.offeredDraw) {
15335         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15336     } else {
15337         if (first.sendDrawOffers) {
15338             SendToProgram("draw\n", &first);
15339             userOfferedDraw = TRUE;
15340         }
15341     }
15342 }
15343
15344 void
15345 AdjournEvent ()
15346 {
15347     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15348
15349     if (appData.icsActive) {
15350         SendToICS(ics_prefix);
15351         SendToICS("adjourn\n");
15352     } else {
15353         /* Currently GNU Chess doesn't offer or accept Adjourns */
15354     }
15355 }
15356
15357
15358 void
15359 AbortEvent ()
15360 {
15361     /* Offer Abort or accept pending Abort offer from opponent */
15362
15363     if (appData.icsActive) {
15364         SendToICS(ics_prefix);
15365         SendToICS("abort\n");
15366     } else {
15367         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15368     }
15369 }
15370
15371 void
15372 ResignEvent ()
15373 {
15374     /* Resign.  You can do this even if it's not your turn. */
15375
15376     if (appData.icsActive) {
15377         SendToICS(ics_prefix);
15378         SendToICS("resign\n");
15379     } else {
15380         switch (gameMode) {
15381           case MachinePlaysWhite:
15382             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15383             break;
15384           case MachinePlaysBlack:
15385             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15386             break;
15387           case EditGame:
15388             if (cmailMsgLoaded) {
15389                 TruncateGame();
15390                 if (WhiteOnMove(cmailOldMove)) {
15391                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15392                 } else {
15393                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15394                 }
15395                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15396             }
15397             break;
15398           default:
15399             break;
15400         }
15401     }
15402 }
15403
15404
15405 void
15406 StopObservingEvent ()
15407 {
15408     /* Stop observing current games */
15409     SendToICS(ics_prefix);
15410     SendToICS("unobserve\n");
15411 }
15412
15413 void
15414 StopExaminingEvent ()
15415 {
15416     /* Stop observing current game */
15417     SendToICS(ics_prefix);
15418     SendToICS("unexamine\n");
15419 }
15420
15421 void
15422 ForwardInner (int target)
15423 {
15424     int limit; int oldSeekGraphUp = seekGraphUp;
15425
15426     if (appData.debugMode)
15427         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15428                 target, currentMove, forwardMostMove);
15429
15430     if (gameMode == EditPosition)
15431       return;
15432
15433     seekGraphUp = FALSE;
15434     MarkTargetSquares(1);
15435
15436     if (gameMode == PlayFromGameFile && !pausing)
15437       PauseEvent();
15438
15439     if (gameMode == IcsExamining && pausing)
15440       limit = pauseExamForwardMostMove;
15441     else
15442       limit = forwardMostMove;
15443
15444     if (target > limit) target = limit;
15445
15446     if (target > 0 && moveList[target - 1][0]) {
15447         int fromX, fromY, toX, toY;
15448         toX = moveList[target - 1][2] - AAA;
15449         toY = moveList[target - 1][3] - ONE;
15450         if (moveList[target - 1][1] == '@') {
15451             if (appData.highlightLastMove) {
15452                 SetHighlights(-1, -1, toX, toY);
15453             }
15454         } else {
15455             fromX = moveList[target - 1][0] - AAA;
15456             fromY = moveList[target - 1][1] - ONE;
15457             if (target == currentMove + 1) {
15458                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15459             }
15460             if (appData.highlightLastMove) {
15461                 SetHighlights(fromX, fromY, toX, toY);
15462             }
15463         }
15464     }
15465     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15466         gameMode == Training || gameMode == PlayFromGameFile ||
15467         gameMode == AnalyzeFile) {
15468         while (currentMove < target) {
15469             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15470             SendMoveToProgram(currentMove++, &first);
15471         }
15472     } else {
15473         currentMove = target;
15474     }
15475
15476     if (gameMode == EditGame || gameMode == EndOfGame) {
15477         whiteTimeRemaining = timeRemaining[0][currentMove];
15478         blackTimeRemaining = timeRemaining[1][currentMove];
15479     }
15480     DisplayBothClocks();
15481     DisplayMove(currentMove - 1);
15482     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15483     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15484     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15485         DisplayComment(currentMove - 1, commentList[currentMove]);
15486     }
15487     ClearMap(); // [HGM] exclude: invalidate map
15488 }
15489
15490
15491 void
15492 ForwardEvent ()
15493 {
15494     if (gameMode == IcsExamining && !pausing) {
15495         SendToICS(ics_prefix);
15496         SendToICS("forward\n");
15497     } else {
15498         ForwardInner(currentMove + 1);
15499     }
15500 }
15501
15502 void
15503 ToEndEvent ()
15504 {
15505     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15506         /* to optimze, we temporarily turn off analysis mode while we feed
15507          * the remaining moves to the engine. Otherwise we get analysis output
15508          * after each move.
15509          */
15510         if (first.analysisSupport) {
15511           SendToProgram("exit\nforce\n", &first);
15512           first.analyzing = FALSE;
15513         }
15514     }
15515
15516     if (gameMode == IcsExamining && !pausing) {
15517         SendToICS(ics_prefix);
15518         SendToICS("forward 999999\n");
15519     } else {
15520         ForwardInner(forwardMostMove);
15521     }
15522
15523     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15524         /* we have fed all the moves, so reactivate analysis mode */
15525         SendToProgram("analyze\n", &first);
15526         first.analyzing = TRUE;
15527         /*first.maybeThinking = TRUE;*/
15528         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15529     }
15530 }
15531
15532 void
15533 BackwardInner (int target)
15534 {
15535     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15536
15537     if (appData.debugMode)
15538         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15539                 target, currentMove, forwardMostMove);
15540
15541     if (gameMode == EditPosition) return;
15542     seekGraphUp = FALSE;
15543     MarkTargetSquares(1);
15544     if (currentMove <= backwardMostMove) {
15545         ClearHighlights();
15546         DrawPosition(full_redraw, boards[currentMove]);
15547         return;
15548     }
15549     if (gameMode == PlayFromGameFile && !pausing)
15550       PauseEvent();
15551
15552     if (moveList[target][0]) {
15553         int fromX, fromY, toX, toY;
15554         toX = moveList[target][2] - AAA;
15555         toY = moveList[target][3] - ONE;
15556         if (moveList[target][1] == '@') {
15557             if (appData.highlightLastMove) {
15558                 SetHighlights(-1, -1, toX, toY);
15559             }
15560         } else {
15561             fromX = moveList[target][0] - AAA;
15562             fromY = moveList[target][1] - ONE;
15563             if (target == currentMove - 1) {
15564                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15565             }
15566             if (appData.highlightLastMove) {
15567                 SetHighlights(fromX, fromY, toX, toY);
15568             }
15569         }
15570     }
15571     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15572         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15573         while (currentMove > target) {
15574             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15575                 // null move cannot be undone. Reload program with move history before it.
15576                 int i;
15577                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15578                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15579                 }
15580                 SendBoard(&first, i);
15581               if(second.analyzing) SendBoard(&second, i);
15582                 for(currentMove=i; currentMove<target; currentMove++) {
15583                     SendMoveToProgram(currentMove, &first);
15584                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15585                 }
15586                 break;
15587             }
15588             SendToBoth("undo\n");
15589             currentMove--;
15590         }
15591     } else {
15592         currentMove = target;
15593     }
15594
15595     if (gameMode == EditGame || gameMode == EndOfGame) {
15596         whiteTimeRemaining = timeRemaining[0][currentMove];
15597         blackTimeRemaining = timeRemaining[1][currentMove];
15598     }
15599     DisplayBothClocks();
15600     DisplayMove(currentMove - 1);
15601     DrawPosition(full_redraw, boards[currentMove]);
15602     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15603     // [HGM] PV info: routine tests if comment empty
15604     DisplayComment(currentMove - 1, commentList[currentMove]);
15605     ClearMap(); // [HGM] exclude: invalidate map
15606 }
15607
15608 void
15609 BackwardEvent ()
15610 {
15611     if (gameMode == IcsExamining && !pausing) {
15612         SendToICS(ics_prefix);
15613         SendToICS("backward\n");
15614     } else {
15615         BackwardInner(currentMove - 1);
15616     }
15617 }
15618
15619 void
15620 ToStartEvent ()
15621 {
15622     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15623         /* to optimize, we temporarily turn off analysis mode while we undo
15624          * all the moves. Otherwise we get analysis output after each undo.
15625          */
15626         if (first.analysisSupport) {
15627           SendToProgram("exit\nforce\n", &first);
15628           first.analyzing = FALSE;
15629         }
15630     }
15631
15632     if (gameMode == IcsExamining && !pausing) {
15633         SendToICS(ics_prefix);
15634         SendToICS("backward 999999\n");
15635     } else {
15636         BackwardInner(backwardMostMove);
15637     }
15638
15639     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15640         /* we have fed all the moves, so reactivate analysis mode */
15641         SendToProgram("analyze\n", &first);
15642         first.analyzing = TRUE;
15643         /*first.maybeThinking = TRUE;*/
15644         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15645     }
15646 }
15647
15648 void
15649 ToNrEvent (int to)
15650 {
15651   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15652   if (to >= forwardMostMove) to = forwardMostMove;
15653   if (to <= backwardMostMove) to = backwardMostMove;
15654   if (to < currentMove) {
15655     BackwardInner(to);
15656   } else {
15657     ForwardInner(to);
15658   }
15659 }
15660
15661 void
15662 RevertEvent (Boolean annotate)
15663 {
15664     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15665         return;
15666     }
15667     if (gameMode != IcsExamining) {
15668         DisplayError(_("You are not examining a game"), 0);
15669         return;
15670     }
15671     if (pausing) {
15672         DisplayError(_("You can't revert while pausing"), 0);
15673         return;
15674     }
15675     SendToICS(ics_prefix);
15676     SendToICS("revert\n");
15677 }
15678
15679 void
15680 RetractMoveEvent ()
15681 {
15682     switch (gameMode) {
15683       case MachinePlaysWhite:
15684       case MachinePlaysBlack:
15685         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15686             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15687             return;
15688         }
15689         if (forwardMostMove < 2) return;
15690         currentMove = forwardMostMove = forwardMostMove - 2;
15691         whiteTimeRemaining = timeRemaining[0][currentMove];
15692         blackTimeRemaining = timeRemaining[1][currentMove];
15693         DisplayBothClocks();
15694         DisplayMove(currentMove - 1);
15695         ClearHighlights();/*!! could figure this out*/
15696         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15697         SendToProgram("remove\n", &first);
15698         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15699         break;
15700
15701       case BeginningOfGame:
15702       default:
15703         break;
15704
15705       case IcsPlayingWhite:
15706       case IcsPlayingBlack:
15707         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15708             SendToICS(ics_prefix);
15709             SendToICS("takeback 2\n");
15710         } else {
15711             SendToICS(ics_prefix);
15712             SendToICS("takeback 1\n");
15713         }
15714         break;
15715     }
15716 }
15717
15718 void
15719 MoveNowEvent ()
15720 {
15721     ChessProgramState *cps;
15722
15723     switch (gameMode) {
15724       case MachinePlaysWhite:
15725         if (!WhiteOnMove(forwardMostMove)) {
15726             DisplayError(_("It is your turn"), 0);
15727             return;
15728         }
15729         cps = &first;
15730         break;
15731       case MachinePlaysBlack:
15732         if (WhiteOnMove(forwardMostMove)) {
15733             DisplayError(_("It is your turn"), 0);
15734             return;
15735         }
15736         cps = &first;
15737         break;
15738       case TwoMachinesPlay:
15739         if (WhiteOnMove(forwardMostMove) ==
15740             (first.twoMachinesColor[0] == 'w')) {
15741             cps = &first;
15742         } else {
15743             cps = &second;
15744         }
15745         break;
15746       case BeginningOfGame:
15747       default:
15748         return;
15749     }
15750     SendToProgram("?\n", cps);
15751 }
15752
15753 void
15754 TruncateGameEvent ()
15755 {
15756     EditGameEvent();
15757     if (gameMode != EditGame) return;
15758     TruncateGame();
15759 }
15760
15761 void
15762 TruncateGame ()
15763 {
15764     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15765     if (forwardMostMove > currentMove) {
15766         if (gameInfo.resultDetails != NULL) {
15767             free(gameInfo.resultDetails);
15768             gameInfo.resultDetails = NULL;
15769             gameInfo.result = GameUnfinished;
15770         }
15771         forwardMostMove = currentMove;
15772         HistorySet(parseList, backwardMostMove, forwardMostMove,
15773                    currentMove-1);
15774     }
15775 }
15776
15777 void
15778 HintEvent ()
15779 {
15780     if (appData.noChessProgram) return;
15781     switch (gameMode) {
15782       case MachinePlaysWhite:
15783         if (WhiteOnMove(forwardMostMove)) {
15784             DisplayError(_("Wait until your turn."), 0);
15785             return;
15786         }
15787         break;
15788       case BeginningOfGame:
15789       case MachinePlaysBlack:
15790         if (!WhiteOnMove(forwardMostMove)) {
15791             DisplayError(_("Wait until your turn."), 0);
15792             return;
15793         }
15794         break;
15795       default:
15796         DisplayError(_("No hint available"), 0);
15797         return;
15798     }
15799     SendToProgram("hint\n", &first);
15800     hintRequested = TRUE;
15801 }
15802
15803 void
15804 CreateBookEvent ()
15805 {
15806     ListGame * lg = (ListGame *) gameList.head;
15807     FILE *f, *g;
15808     int nItem;
15809     static int secondTime = FALSE;
15810
15811     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15812         DisplayError(_("Game list not loaded or empty"), 0);
15813         return;
15814     }
15815
15816     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15817         fclose(g);
15818         secondTime++;
15819         DisplayNote(_("Book file exists! Try again for overwrite."));
15820         return;
15821     }
15822
15823     creatingBook = TRUE;
15824     secondTime = FALSE;
15825
15826     /* Get list size */
15827     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15828         LoadGame(f, nItem, "", TRUE);
15829         AddGameToBook(TRUE);
15830         lg = (ListGame *) lg->node.succ;
15831     }
15832
15833     creatingBook = FALSE;
15834     FlushBook();
15835 }
15836
15837 void
15838 BookEvent ()
15839 {
15840     if (appData.noChessProgram) return;
15841     switch (gameMode) {
15842       case MachinePlaysWhite:
15843         if (WhiteOnMove(forwardMostMove)) {
15844             DisplayError(_("Wait until your turn."), 0);
15845             return;
15846         }
15847         break;
15848       case BeginningOfGame:
15849       case MachinePlaysBlack:
15850         if (!WhiteOnMove(forwardMostMove)) {
15851             DisplayError(_("Wait until your turn."), 0);
15852             return;
15853         }
15854         break;
15855       case EditPosition:
15856         EditPositionDone(TRUE);
15857         break;
15858       case TwoMachinesPlay:
15859         return;
15860       default:
15861         break;
15862     }
15863     SendToProgram("bk\n", &first);
15864     bookOutput[0] = NULLCHAR;
15865     bookRequested = TRUE;
15866 }
15867
15868 void
15869 AboutGameEvent ()
15870 {
15871     char *tags = PGNTags(&gameInfo);
15872     TagsPopUp(tags, CmailMsg());
15873     free(tags);
15874 }
15875
15876 /* end button procedures */
15877
15878 void
15879 PrintPosition (FILE *fp, int move)
15880 {
15881     int i, j;
15882
15883     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15884         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15885             char c = PieceToChar(boards[move][i][j]);
15886             fputc(c == 'x' ? '.' : c, fp);
15887             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15888         }
15889     }
15890     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15891       fprintf(fp, "white to play\n");
15892     else
15893       fprintf(fp, "black to play\n");
15894 }
15895
15896 void
15897 PrintOpponents (FILE *fp)
15898 {
15899     if (gameInfo.white != NULL) {
15900         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15901     } else {
15902         fprintf(fp, "\n");
15903     }
15904 }
15905
15906 /* Find last component of program's own name, using some heuristics */
15907 void
15908 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15909 {
15910     char *p, *q, c;
15911     int local = (strcmp(host, "localhost") == 0);
15912     while (!local && (p = strchr(prog, ';')) != NULL) {
15913         p++;
15914         while (*p == ' ') p++;
15915         prog = p;
15916     }
15917     if (*prog == '"' || *prog == '\'') {
15918         q = strchr(prog + 1, *prog);
15919     } else {
15920         q = strchr(prog, ' ');
15921     }
15922     if (q == NULL) q = prog + strlen(prog);
15923     p = q;
15924     while (p >= prog && *p != '/' && *p != '\\') p--;
15925     p++;
15926     if(p == prog && *p == '"') p++;
15927     c = *q; *q = 0;
15928     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15929     memcpy(buf, p, q - p);
15930     buf[q - p] = NULLCHAR;
15931     if (!local) {
15932         strcat(buf, "@");
15933         strcat(buf, host);
15934     }
15935 }
15936
15937 char *
15938 TimeControlTagValue ()
15939 {
15940     char buf[MSG_SIZ];
15941     if (!appData.clockMode) {
15942       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15943     } else if (movesPerSession > 0) {
15944       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15945     } else if (timeIncrement == 0) {
15946       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15947     } else {
15948       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15949     }
15950     return StrSave(buf);
15951 }
15952
15953 void
15954 SetGameInfo ()
15955 {
15956     /* This routine is used only for certain modes */
15957     VariantClass v = gameInfo.variant;
15958     ChessMove r = GameUnfinished;
15959     char *p = NULL;
15960
15961     if(keepInfo) return;
15962
15963     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15964         r = gameInfo.result;
15965         p = gameInfo.resultDetails;
15966         gameInfo.resultDetails = NULL;
15967     }
15968     ClearGameInfo(&gameInfo);
15969     gameInfo.variant = v;
15970
15971     switch (gameMode) {
15972       case MachinePlaysWhite:
15973         gameInfo.event = StrSave( appData.pgnEventHeader );
15974         gameInfo.site = StrSave(HostName());
15975         gameInfo.date = PGNDate();
15976         gameInfo.round = StrSave("-");
15977         gameInfo.white = StrSave(first.tidy);
15978         gameInfo.black = StrSave(UserName());
15979         gameInfo.timeControl = TimeControlTagValue();
15980         break;
15981
15982       case MachinePlaysBlack:
15983         gameInfo.event = StrSave( appData.pgnEventHeader );
15984         gameInfo.site = StrSave(HostName());
15985         gameInfo.date = PGNDate();
15986         gameInfo.round = StrSave("-");
15987         gameInfo.white = StrSave(UserName());
15988         gameInfo.black = StrSave(first.tidy);
15989         gameInfo.timeControl = TimeControlTagValue();
15990         break;
15991
15992       case TwoMachinesPlay:
15993         gameInfo.event = StrSave( appData.pgnEventHeader );
15994         gameInfo.site = StrSave(HostName());
15995         gameInfo.date = PGNDate();
15996         if (roundNr > 0) {
15997             char buf[MSG_SIZ];
15998             snprintf(buf, MSG_SIZ, "%d", roundNr);
15999             gameInfo.round = StrSave(buf);
16000         } else {
16001             gameInfo.round = StrSave("-");
16002         }
16003         if (first.twoMachinesColor[0] == 'w') {
16004             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16005             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16006         } else {
16007             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16008             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16009         }
16010         gameInfo.timeControl = TimeControlTagValue();
16011         break;
16012
16013       case EditGame:
16014         gameInfo.event = StrSave("Edited game");
16015         gameInfo.site = StrSave(HostName());
16016         gameInfo.date = PGNDate();
16017         gameInfo.round = StrSave("-");
16018         gameInfo.white = StrSave("-");
16019         gameInfo.black = StrSave("-");
16020         gameInfo.result = r;
16021         gameInfo.resultDetails = p;
16022         break;
16023
16024       case EditPosition:
16025         gameInfo.event = StrSave("Edited position");
16026         gameInfo.site = StrSave(HostName());
16027         gameInfo.date = PGNDate();
16028         gameInfo.round = StrSave("-");
16029         gameInfo.white = StrSave("-");
16030         gameInfo.black = StrSave("-");
16031         break;
16032
16033       case IcsPlayingWhite:
16034       case IcsPlayingBlack:
16035       case IcsObserving:
16036       case IcsExamining:
16037         break;
16038
16039       case PlayFromGameFile:
16040         gameInfo.event = StrSave("Game from non-PGN file");
16041         gameInfo.site = StrSave(HostName());
16042         gameInfo.date = PGNDate();
16043         gameInfo.round = StrSave("-");
16044         gameInfo.white = StrSave("?");
16045         gameInfo.black = StrSave("?");
16046         break;
16047
16048       default:
16049         break;
16050     }
16051 }
16052
16053 void
16054 ReplaceComment (int index, char *text)
16055 {
16056     int len;
16057     char *p;
16058     float score;
16059
16060     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16061        pvInfoList[index-1].depth == len &&
16062        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16063        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16064     while (*text == '\n') text++;
16065     len = strlen(text);
16066     while (len > 0 && text[len - 1] == '\n') len--;
16067
16068     if (commentList[index] != NULL)
16069       free(commentList[index]);
16070
16071     if (len == 0) {
16072         commentList[index] = NULL;
16073         return;
16074     }
16075   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16076       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16077       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16078     commentList[index] = (char *) malloc(len + 2);
16079     strncpy(commentList[index], text, len);
16080     commentList[index][len] = '\n';
16081     commentList[index][len + 1] = NULLCHAR;
16082   } else {
16083     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16084     char *p;
16085     commentList[index] = (char *) malloc(len + 7);
16086     safeStrCpy(commentList[index], "{\n", 3);
16087     safeStrCpy(commentList[index]+2, text, len+1);
16088     commentList[index][len+2] = NULLCHAR;
16089     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16090     strcat(commentList[index], "\n}\n");
16091   }
16092 }
16093
16094 void
16095 CrushCRs (char *text)
16096 {
16097   char *p = text;
16098   char *q = text;
16099   char ch;
16100
16101   do {
16102     ch = *p++;
16103     if (ch == '\r') continue;
16104     *q++ = ch;
16105   } while (ch != '\0');
16106 }
16107
16108 void
16109 AppendComment (int index, char *text, Boolean addBraces)
16110 /* addBraces  tells if we should add {} */
16111 {
16112     int oldlen, len;
16113     char *old;
16114
16115 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16116     if(addBraces == 3) addBraces = 0; else // force appending literally
16117     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16118
16119     CrushCRs(text);
16120     while (*text == '\n') text++;
16121     len = strlen(text);
16122     while (len > 0 && text[len - 1] == '\n') len--;
16123     text[len] = NULLCHAR;
16124
16125     if (len == 0) return;
16126
16127     if (commentList[index] != NULL) {
16128       Boolean addClosingBrace = addBraces;
16129         old = commentList[index];
16130         oldlen = strlen(old);
16131         while(commentList[index][oldlen-1] ==  '\n')
16132           commentList[index][--oldlen] = NULLCHAR;
16133         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16134         safeStrCpy(commentList[index], old, oldlen + len + 6);
16135         free(old);
16136         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16137         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16138           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16139           while (*text == '\n') { text++; len--; }
16140           commentList[index][--oldlen] = NULLCHAR;
16141       }
16142         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16143         else          strcat(commentList[index], "\n");
16144         strcat(commentList[index], text);
16145         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16146         else          strcat(commentList[index], "\n");
16147     } else {
16148         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16149         if(addBraces)
16150           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16151         else commentList[index][0] = NULLCHAR;
16152         strcat(commentList[index], text);
16153         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16154         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16155     }
16156 }
16157
16158 static char *
16159 FindStr (char * text, char * sub_text)
16160 {
16161     char * result = strstr( text, sub_text );
16162
16163     if( result != NULL ) {
16164         result += strlen( sub_text );
16165     }
16166
16167     return result;
16168 }
16169
16170 /* [AS] Try to extract PV info from PGN comment */
16171 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16172 char *
16173 GetInfoFromComment (int index, char * text)
16174 {
16175     char * sep = text, *p;
16176
16177     if( text != NULL && index > 0 ) {
16178         int score = 0;
16179         int depth = 0;
16180         int time = -1, sec = 0, deci;
16181         char * s_eval = FindStr( text, "[%eval " );
16182         char * s_emt = FindStr( text, "[%emt " );
16183 #if 0
16184         if( s_eval != NULL || s_emt != NULL ) {
16185 #else
16186         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16187 #endif
16188             /* New style */
16189             char delim;
16190
16191             if( s_eval != NULL ) {
16192                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16193                     return text;
16194                 }
16195
16196                 if( delim != ']' ) {
16197                     return text;
16198                 }
16199             }
16200
16201             if( s_emt != NULL ) {
16202             }
16203                 return text;
16204         }
16205         else {
16206             /* We expect something like: [+|-]nnn.nn/dd */
16207             int score_lo = 0;
16208
16209             if(*text != '{') return text; // [HGM] braces: must be normal comment
16210
16211             sep = strchr( text, '/' );
16212             if( sep == NULL || sep < (text+4) ) {
16213                 return text;
16214             }
16215
16216             p = text;
16217             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16218             if(p[1] == '(') { // comment starts with PV
16219                p = strchr(p, ')'); // locate end of PV
16220                if(p == NULL || sep < p+5) return text;
16221                // at this point we have something like "{(.*) +0.23/6 ..."
16222                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16223                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16224                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16225             }
16226             time = -1; sec = -1; deci = -1;
16227             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16228                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16229                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16230                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16231                 return text;
16232             }
16233
16234             if( score_lo < 0 || score_lo >= 100 ) {
16235                 return text;
16236             }
16237
16238             if(sec >= 0) time = 600*time + 10*sec; else
16239             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16240
16241             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16242
16243             /* [HGM] PV time: now locate end of PV info */
16244             while( *++sep >= '0' && *sep <= '9'); // strip depth
16245             if(time >= 0)
16246             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16247             if(sec >= 0)
16248             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16249             if(deci >= 0)
16250             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16251             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16252         }
16253
16254         if( depth <= 0 ) {
16255             return text;
16256         }
16257
16258         if( time < 0 ) {
16259             time = -1;
16260         }
16261
16262         pvInfoList[index-1].depth = depth;
16263         pvInfoList[index-1].score = score;
16264         pvInfoList[index-1].time  = 10*time; // centi-sec
16265         if(*sep == '}') *sep = 0; else *--sep = '{';
16266         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16267     }
16268     return sep;
16269 }
16270
16271 void
16272 SendToProgram (char *message, ChessProgramState *cps)
16273 {
16274     int count, outCount, error;
16275     char buf[MSG_SIZ];
16276
16277     if (cps->pr == NoProc) return;
16278     Attention(cps);
16279
16280     if (appData.debugMode) {
16281         TimeMark now;
16282         GetTimeMark(&now);
16283         fprintf(debugFP, "%ld >%-6s: %s",
16284                 SubtractTimeMarks(&now, &programStartTime),
16285                 cps->which, message);
16286         if(serverFP)
16287             fprintf(serverFP, "%ld >%-6s: %s",
16288                 SubtractTimeMarks(&now, &programStartTime),
16289                 cps->which, message), fflush(serverFP);
16290     }
16291
16292     count = strlen(message);
16293     outCount = OutputToProcess(cps->pr, message, count, &error);
16294     if (outCount < count && !exiting
16295                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16296       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16297       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16298         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16299             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16300                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16301                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16302                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16303             } else {
16304                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16305                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16306                 gameInfo.result = res;
16307             }
16308             gameInfo.resultDetails = StrSave(buf);
16309         }
16310         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16311         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16312     }
16313 }
16314
16315 void
16316 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16317 {
16318     char *end_str;
16319     char buf[MSG_SIZ];
16320     ChessProgramState *cps = (ChessProgramState *)closure;
16321
16322     if (isr != cps->isr) return; /* Killed intentionally */
16323     if (count <= 0) {
16324         if (count == 0) {
16325             RemoveInputSource(cps->isr);
16326             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16327                     _(cps->which), cps->program);
16328             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16329             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16330                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16331                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16332                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16333                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16334                 } else {
16335                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16336                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16337                     gameInfo.result = res;
16338                 }
16339                 gameInfo.resultDetails = StrSave(buf);
16340             }
16341             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16342             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16343         } else {
16344             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16345                     _(cps->which), cps->program);
16346             RemoveInputSource(cps->isr);
16347
16348             /* [AS] Program is misbehaving badly... kill it */
16349             if( count == -2 ) {
16350                 DestroyChildProcess( cps->pr, 9 );
16351                 cps->pr = NoProc;
16352             }
16353
16354             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16355         }
16356         return;
16357     }
16358
16359     if ((end_str = strchr(message, '\r')) != NULL)
16360       *end_str = NULLCHAR;
16361     if ((end_str = strchr(message, '\n')) != NULL)
16362       *end_str = NULLCHAR;
16363
16364     if (appData.debugMode) {
16365         TimeMark now; int print = 1;
16366         char *quote = ""; char c; int i;
16367
16368         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16369                 char start = message[0];
16370                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16371                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16372                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16373                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16374                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16375                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16376                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16377                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16378                    sscanf(message, "hint: %c", &c)!=1 &&
16379                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16380                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16381                     print = (appData.engineComments >= 2);
16382                 }
16383                 message[0] = start; // restore original message
16384         }
16385         if(print) {
16386                 GetTimeMark(&now);
16387                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16388                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16389                         quote,
16390                         message);
16391                 if(serverFP)
16392                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16393                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16394                         quote,
16395                         message), fflush(serverFP);
16396         }
16397     }
16398
16399     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16400     if (appData.icsEngineAnalyze) {
16401         if (strstr(message, "whisper") != NULL ||
16402              strstr(message, "kibitz") != NULL ||
16403             strstr(message, "tellics") != NULL) return;
16404     }
16405
16406     HandleMachineMove(message, cps);
16407 }
16408
16409
16410 void
16411 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16412 {
16413     char buf[MSG_SIZ];
16414     int seconds;
16415
16416     if( timeControl_2 > 0 ) {
16417         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16418             tc = timeControl_2;
16419         }
16420     }
16421     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16422     inc /= cps->timeOdds;
16423     st  /= cps->timeOdds;
16424
16425     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16426
16427     if (st > 0) {
16428       /* Set exact time per move, normally using st command */
16429       if (cps->stKludge) {
16430         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16431         seconds = st % 60;
16432         if (seconds == 0) {
16433           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16434         } else {
16435           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16436         }
16437       } else {
16438         snprintf(buf, MSG_SIZ, "st %d\n", st);
16439       }
16440     } else {
16441       /* Set conventional or incremental time control, using level command */
16442       if (seconds == 0) {
16443         /* Note old gnuchess bug -- minutes:seconds used to not work.
16444            Fixed in later versions, but still avoid :seconds
16445            when seconds is 0. */
16446         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16447       } else {
16448         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16449                  seconds, inc/1000.);
16450       }
16451     }
16452     SendToProgram(buf, cps);
16453
16454     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16455     /* Orthogonally, limit search to given depth */
16456     if (sd > 0) {
16457       if (cps->sdKludge) {
16458         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16459       } else {
16460         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16461       }
16462       SendToProgram(buf, cps);
16463     }
16464
16465     if(cps->nps >= 0) { /* [HGM] nps */
16466         if(cps->supportsNPS == FALSE)
16467           cps->nps = -1; // don't use if engine explicitly says not supported!
16468         else {
16469           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16470           SendToProgram(buf, cps);
16471         }
16472     }
16473 }
16474
16475 ChessProgramState *
16476 WhitePlayer ()
16477 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16478 {
16479     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16480        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16481         return &second;
16482     return &first;
16483 }
16484
16485 void
16486 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16487 {
16488     char message[MSG_SIZ];
16489     long time, otime;
16490
16491     /* Note: this routine must be called when the clocks are stopped
16492        or when they have *just* been set or switched; otherwise
16493        it will be off by the time since the current tick started.
16494     */
16495     if (machineWhite) {
16496         time = whiteTimeRemaining / 10;
16497         otime = blackTimeRemaining / 10;
16498     } else {
16499         time = blackTimeRemaining / 10;
16500         otime = whiteTimeRemaining / 10;
16501     }
16502     /* [HGM] translate opponent's time by time-odds factor */
16503     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16504
16505     if (time <= 0) time = 1;
16506     if (otime <= 0) otime = 1;
16507
16508     snprintf(message, MSG_SIZ, "time %ld\n", time);
16509     SendToProgram(message, cps);
16510
16511     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16512     SendToProgram(message, cps);
16513 }
16514
16515 char *
16516 EngineDefinedVariant (ChessProgramState *cps, int n)
16517 {   // return name of n-th unknown variant that engine supports
16518     static char buf[MSG_SIZ];
16519     char *p, *s = cps->variants;
16520     if(!s) return NULL;
16521     do { // parse string from variants feature
16522       VariantClass v;
16523         p = strchr(s, ',');
16524         if(p) *p = NULLCHAR;
16525       v = StringToVariant(s);
16526       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16527         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16528             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16529         }
16530         if(p) *p++ = ',';
16531         if(n < 0) return buf;
16532     } while(s = p);
16533     return NULL;
16534 }
16535
16536 int
16537 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16538 {
16539   char buf[MSG_SIZ];
16540   int len = strlen(name);
16541   int val;
16542
16543   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16544     (*p) += len + 1;
16545     sscanf(*p, "%d", &val);
16546     *loc = (val != 0);
16547     while (**p && **p != ' ')
16548       (*p)++;
16549     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16550     SendToProgram(buf, cps);
16551     return TRUE;
16552   }
16553   return FALSE;
16554 }
16555
16556 int
16557 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16558 {
16559   char buf[MSG_SIZ];
16560   int len = strlen(name);
16561   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16562     (*p) += len + 1;
16563     sscanf(*p, "%d", loc);
16564     while (**p && **p != ' ') (*p)++;
16565     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16566     SendToProgram(buf, cps);
16567     return TRUE;
16568   }
16569   return FALSE;
16570 }
16571
16572 int
16573 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16574 {
16575   char buf[MSG_SIZ];
16576   int len = strlen(name);
16577   if (strncmp((*p), name, len) == 0
16578       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16579     (*p) += len + 2;
16580     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16581     sscanf(*p, "%[^\"]", *loc);
16582     while (**p && **p != '\"') (*p)++;
16583     if (**p == '\"') (*p)++;
16584     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16585     SendToProgram(buf, cps);
16586     return TRUE;
16587   }
16588   return FALSE;
16589 }
16590
16591 int
16592 ParseOption (Option *opt, ChessProgramState *cps)
16593 // [HGM] options: process the string that defines an engine option, and determine
16594 // name, type, default value, and allowed value range
16595 {
16596         char *p, *q, buf[MSG_SIZ];
16597         int n, min = (-1)<<31, max = 1<<31, def;
16598
16599         if(p = strstr(opt->name, " -spin ")) {
16600             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16601             if(max < min) max = min; // enforce consistency
16602             if(def < min) def = min;
16603             if(def > max) def = max;
16604             opt->value = def;
16605             opt->min = min;
16606             opt->max = max;
16607             opt->type = Spin;
16608         } else if((p = strstr(opt->name, " -slider "))) {
16609             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16610             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16611             if(max < min) max = min; // enforce consistency
16612             if(def < min) def = min;
16613             if(def > max) def = max;
16614             opt->value = def;
16615             opt->min = min;
16616             opt->max = max;
16617             opt->type = Spin; // Slider;
16618         } else if((p = strstr(opt->name, " -string "))) {
16619             opt->textValue = p+9;
16620             opt->type = TextBox;
16621         } else if((p = strstr(opt->name, " -file "))) {
16622             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16623             opt->textValue = p+7;
16624             opt->type = FileName; // FileName;
16625         } else if((p = strstr(opt->name, " -path "))) {
16626             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16627             opt->textValue = p+7;
16628             opt->type = PathName; // PathName;
16629         } else if(p = strstr(opt->name, " -check ")) {
16630             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16631             opt->value = (def != 0);
16632             opt->type = CheckBox;
16633         } else if(p = strstr(opt->name, " -combo ")) {
16634             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16635             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16636             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16637             opt->value = n = 0;
16638             while(q = StrStr(q, " /// ")) {
16639                 n++; *q = 0;    // count choices, and null-terminate each of them
16640                 q += 5;
16641                 if(*q == '*') { // remember default, which is marked with * prefix
16642                     q++;
16643                     opt->value = n;
16644                 }
16645                 cps->comboList[cps->comboCnt++] = q;
16646             }
16647             cps->comboList[cps->comboCnt++] = NULL;
16648             opt->max = n + 1;
16649             opt->type = ComboBox;
16650         } else if(p = strstr(opt->name, " -button")) {
16651             opt->type = Button;
16652         } else if(p = strstr(opt->name, " -save")) {
16653             opt->type = SaveButton;
16654         } else return FALSE;
16655         *p = 0; // terminate option name
16656         // now look if the command-line options define a setting for this engine option.
16657         if(cps->optionSettings && cps->optionSettings[0])
16658             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16659         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16660           snprintf(buf, MSG_SIZ, "option %s", p);
16661                 if(p = strstr(buf, ",")) *p = 0;
16662                 if(q = strchr(buf, '=')) switch(opt->type) {
16663                     case ComboBox:
16664                         for(n=0; n<opt->max; n++)
16665                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16666                         break;
16667                     case TextBox:
16668                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16669                         break;
16670                     case Spin:
16671                     case CheckBox:
16672                         opt->value = atoi(q+1);
16673                     default:
16674                         break;
16675                 }
16676                 strcat(buf, "\n");
16677                 SendToProgram(buf, cps);
16678         }
16679         return TRUE;
16680 }
16681
16682 void
16683 FeatureDone (ChessProgramState *cps, int val)
16684 {
16685   DelayedEventCallback cb = GetDelayedEvent();
16686   if ((cb == InitBackEnd3 && cps == &first) ||
16687       (cb == SettingsMenuIfReady && cps == &second) ||
16688       (cb == LoadEngine) ||
16689       (cb == TwoMachinesEventIfReady)) {
16690     CancelDelayedEvent();
16691     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16692   }
16693   cps->initDone = val;
16694   if(val) cps->reload = FALSE;
16695 }
16696
16697 /* Parse feature command from engine */
16698 void
16699 ParseFeatures (char *args, ChessProgramState *cps)
16700 {
16701   char *p = args;
16702   char *q = NULL;
16703   int val;
16704   char buf[MSG_SIZ];
16705
16706   for (;;) {
16707     while (*p == ' ') p++;
16708     if (*p == NULLCHAR) return;
16709
16710     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16711     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16712     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16713     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16714     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16715     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16716     if (BoolFeature(&p, "reuse", &val, cps)) {
16717       /* Engine can disable reuse, but can't enable it if user said no */
16718       if (!val) cps->reuse = FALSE;
16719       continue;
16720     }
16721     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16722     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16723       if (gameMode == TwoMachinesPlay) {
16724         DisplayTwoMachinesTitle();
16725       } else {
16726         DisplayTitle("");
16727       }
16728       continue;
16729     }
16730     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16731     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16732     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16733     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16734     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16735     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16736     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16737     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16738     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16739     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16740     if (IntFeature(&p, "done", &val, cps)) {
16741       FeatureDone(cps, val);
16742       continue;
16743     }
16744     /* Added by Tord: */
16745     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16746     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16747     /* End of additions by Tord */
16748
16749     /* [HGM] added features: */
16750     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16751     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16752     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16753     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16754     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16755     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16756     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16757     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16758         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16759         FREE(cps->option[cps->nrOptions].name);
16760         cps->option[cps->nrOptions].name = q; q = NULL;
16761         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16762           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16763             SendToProgram(buf, cps);
16764             continue;
16765         }
16766         if(cps->nrOptions >= MAX_OPTIONS) {
16767             cps->nrOptions--;
16768             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16769             DisplayError(buf, 0);
16770         }
16771         continue;
16772     }
16773     /* End of additions by HGM */
16774
16775     /* unknown feature: complain and skip */
16776     q = p;
16777     while (*q && *q != '=') q++;
16778     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16779     SendToProgram(buf, cps);
16780     p = q;
16781     if (*p == '=') {
16782       p++;
16783       if (*p == '\"') {
16784         p++;
16785         while (*p && *p != '\"') p++;
16786         if (*p == '\"') p++;
16787       } else {
16788         while (*p && *p != ' ') p++;
16789       }
16790     }
16791   }
16792
16793 }
16794
16795 void
16796 PeriodicUpdatesEvent (int newState)
16797 {
16798     if (newState == appData.periodicUpdates)
16799       return;
16800
16801     appData.periodicUpdates=newState;
16802
16803     /* Display type changes, so update it now */
16804 //    DisplayAnalysis();
16805
16806     /* Get the ball rolling again... */
16807     if (newState) {
16808         AnalysisPeriodicEvent(1);
16809         StartAnalysisClock();
16810     }
16811 }
16812
16813 void
16814 PonderNextMoveEvent (int newState)
16815 {
16816     if (newState == appData.ponderNextMove) return;
16817     if (gameMode == EditPosition) EditPositionDone(TRUE);
16818     if (newState) {
16819         SendToProgram("hard\n", &first);
16820         if (gameMode == TwoMachinesPlay) {
16821             SendToProgram("hard\n", &second);
16822         }
16823     } else {
16824         SendToProgram("easy\n", &first);
16825         thinkOutput[0] = NULLCHAR;
16826         if (gameMode == TwoMachinesPlay) {
16827             SendToProgram("easy\n", &second);
16828         }
16829     }
16830     appData.ponderNextMove = newState;
16831 }
16832
16833 void
16834 NewSettingEvent (int option, int *feature, char *command, int value)
16835 {
16836     char buf[MSG_SIZ];
16837
16838     if (gameMode == EditPosition) EditPositionDone(TRUE);
16839     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16840     if(feature == NULL || *feature) SendToProgram(buf, &first);
16841     if (gameMode == TwoMachinesPlay) {
16842         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16843     }
16844 }
16845
16846 void
16847 ShowThinkingEvent ()
16848 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16849 {
16850     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16851     int newState = appData.showThinking
16852         // [HGM] thinking: other features now need thinking output as well
16853         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16854
16855     if (oldState == newState) return;
16856     oldState = newState;
16857     if (gameMode == EditPosition) EditPositionDone(TRUE);
16858     if (oldState) {
16859         SendToProgram("post\n", &first);
16860         if (gameMode == TwoMachinesPlay) {
16861             SendToProgram("post\n", &second);
16862         }
16863     } else {
16864         SendToProgram("nopost\n", &first);
16865         thinkOutput[0] = NULLCHAR;
16866         if (gameMode == TwoMachinesPlay) {
16867             SendToProgram("nopost\n", &second);
16868         }
16869     }
16870 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16871 }
16872
16873 void
16874 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16875 {
16876   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16877   if (pr == NoProc) return;
16878   AskQuestion(title, question, replyPrefix, pr);
16879 }
16880
16881 void
16882 TypeInEvent (char firstChar)
16883 {
16884     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16885         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16886         gameMode == AnalyzeMode || gameMode == EditGame ||
16887         gameMode == EditPosition || gameMode == IcsExamining ||
16888         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16889         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16890                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16891                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16892         gameMode == Training) PopUpMoveDialog(firstChar);
16893 }
16894
16895 void
16896 TypeInDoneEvent (char *move)
16897 {
16898         Board board;
16899         int n, fromX, fromY, toX, toY;
16900         char promoChar;
16901         ChessMove moveType;
16902
16903         // [HGM] FENedit
16904         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16905                 EditPositionPasteFEN(move);
16906                 return;
16907         }
16908         // [HGM] movenum: allow move number to be typed in any mode
16909         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16910           ToNrEvent(2*n-1);
16911           return;
16912         }
16913         // undocumented kludge: allow command-line option to be typed in!
16914         // (potentially fatal, and does not implement the effect of the option.)
16915         // should only be used for options that are values on which future decisions will be made,
16916         // and definitely not on options that would be used during initialization.
16917         if(strstr(move, "!!! -") == move) {
16918             ParseArgsFromString(move+4);
16919             return;
16920         }
16921
16922       if (gameMode != EditGame && currentMove != forwardMostMove &&
16923         gameMode != Training) {
16924         DisplayMoveError(_("Displayed move is not current"));
16925       } else {
16926         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16927           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16928         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16929         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16930           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16931           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16932         } else {
16933           DisplayMoveError(_("Could not parse move"));
16934         }
16935       }
16936 }
16937
16938 void
16939 DisplayMove (int moveNumber)
16940 {
16941     char message[MSG_SIZ];
16942     char res[MSG_SIZ];
16943     char cpThinkOutput[MSG_SIZ];
16944
16945     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16946
16947     if (moveNumber == forwardMostMove - 1 ||
16948         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16949
16950         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16951
16952         if (strchr(cpThinkOutput, '\n')) {
16953             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16954         }
16955     } else {
16956         *cpThinkOutput = NULLCHAR;
16957     }
16958
16959     /* [AS] Hide thinking from human user */
16960     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16961         *cpThinkOutput = NULLCHAR;
16962         if( thinkOutput[0] != NULLCHAR ) {
16963             int i;
16964
16965             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16966                 cpThinkOutput[i] = '.';
16967             }
16968             cpThinkOutput[i] = NULLCHAR;
16969             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16970         }
16971     }
16972
16973     if (moveNumber == forwardMostMove - 1 &&
16974         gameInfo.resultDetails != NULL) {
16975         if (gameInfo.resultDetails[0] == NULLCHAR) {
16976           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16977         } else {
16978           snprintf(res, MSG_SIZ, " {%s} %s",
16979                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16980         }
16981     } else {
16982         res[0] = NULLCHAR;
16983     }
16984
16985     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16986         DisplayMessage(res, cpThinkOutput);
16987     } else {
16988       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16989                 WhiteOnMove(moveNumber) ? " " : ".. ",
16990                 parseList[moveNumber], res);
16991         DisplayMessage(message, cpThinkOutput);
16992     }
16993 }
16994
16995 void
16996 DisplayComment (int moveNumber, char *text)
16997 {
16998     char title[MSG_SIZ];
16999
17000     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17001       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17002     } else {
17003       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17004               WhiteOnMove(moveNumber) ? " " : ".. ",
17005               parseList[moveNumber]);
17006     }
17007     if (text != NULL && (appData.autoDisplayComment || commentUp))
17008         CommentPopUp(title, text);
17009 }
17010
17011 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17012  * might be busy thinking or pondering.  It can be omitted if your
17013  * gnuchess is configured to stop thinking immediately on any user
17014  * input.  However, that gnuchess feature depends on the FIONREAD
17015  * ioctl, which does not work properly on some flavors of Unix.
17016  */
17017 void
17018 Attention (ChessProgramState *cps)
17019 {
17020 #if ATTENTION
17021     if (!cps->useSigint) return;
17022     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17023     switch (gameMode) {
17024       case MachinePlaysWhite:
17025       case MachinePlaysBlack:
17026       case TwoMachinesPlay:
17027       case IcsPlayingWhite:
17028       case IcsPlayingBlack:
17029       case AnalyzeMode:
17030       case AnalyzeFile:
17031         /* Skip if we know it isn't thinking */
17032         if (!cps->maybeThinking) return;
17033         if (appData.debugMode)
17034           fprintf(debugFP, "Interrupting %s\n", cps->which);
17035         InterruptChildProcess(cps->pr);
17036         cps->maybeThinking = FALSE;
17037         break;
17038       default:
17039         break;
17040     }
17041 #endif /*ATTENTION*/
17042 }
17043
17044 int
17045 CheckFlags ()
17046 {
17047     if (whiteTimeRemaining <= 0) {
17048         if (!whiteFlag) {
17049             whiteFlag = TRUE;
17050             if (appData.icsActive) {
17051                 if (appData.autoCallFlag &&
17052                     gameMode == IcsPlayingBlack && !blackFlag) {
17053                   SendToICS(ics_prefix);
17054                   SendToICS("flag\n");
17055                 }
17056             } else {
17057                 if (blackFlag) {
17058                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17059                 } else {
17060                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17061                     if (appData.autoCallFlag) {
17062                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17063                         return TRUE;
17064                     }
17065                 }
17066             }
17067         }
17068     }
17069     if (blackTimeRemaining <= 0) {
17070         if (!blackFlag) {
17071             blackFlag = TRUE;
17072             if (appData.icsActive) {
17073                 if (appData.autoCallFlag &&
17074                     gameMode == IcsPlayingWhite && !whiteFlag) {
17075                   SendToICS(ics_prefix);
17076                   SendToICS("flag\n");
17077                 }
17078             } else {
17079                 if (whiteFlag) {
17080                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17081                 } else {
17082                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17083                     if (appData.autoCallFlag) {
17084                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17085                         return TRUE;
17086                     }
17087                 }
17088             }
17089         }
17090     }
17091     return FALSE;
17092 }
17093
17094 void
17095 CheckTimeControl ()
17096 {
17097     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17098         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17099
17100     /*
17101      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17102      */
17103     if ( !WhiteOnMove(forwardMostMove) ) {
17104         /* White made time control */
17105         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17106         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17107         /* [HGM] time odds: correct new time quota for time odds! */
17108                                             / WhitePlayer()->timeOdds;
17109         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17110     } else {
17111         lastBlack -= blackTimeRemaining;
17112         /* Black made time control */
17113         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17114                                             / WhitePlayer()->other->timeOdds;
17115         lastWhite = whiteTimeRemaining;
17116     }
17117 }
17118
17119 void
17120 DisplayBothClocks ()
17121 {
17122     int wom = gameMode == EditPosition ?
17123       !blackPlaysFirst : WhiteOnMove(currentMove);
17124     DisplayWhiteClock(whiteTimeRemaining, wom);
17125     DisplayBlackClock(blackTimeRemaining, !wom);
17126 }
17127
17128
17129 /* Timekeeping seems to be a portability nightmare.  I think everyone
17130    has ftime(), but I'm really not sure, so I'm including some ifdefs
17131    to use other calls if you don't.  Clocks will be less accurate if
17132    you have neither ftime nor gettimeofday.
17133 */
17134
17135 /* VS 2008 requires the #include outside of the function */
17136 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17137 #include <sys/timeb.h>
17138 #endif
17139
17140 /* Get the current time as a TimeMark */
17141 void
17142 GetTimeMark (TimeMark *tm)
17143 {
17144 #if HAVE_GETTIMEOFDAY
17145
17146     struct timeval timeVal;
17147     struct timezone timeZone;
17148
17149     gettimeofday(&timeVal, &timeZone);
17150     tm->sec = (long) timeVal.tv_sec;
17151     tm->ms = (int) (timeVal.tv_usec / 1000L);
17152
17153 #else /*!HAVE_GETTIMEOFDAY*/
17154 #if HAVE_FTIME
17155
17156 // include <sys/timeb.h> / moved to just above start of function
17157     struct timeb timeB;
17158
17159     ftime(&timeB);
17160     tm->sec = (long) timeB.time;
17161     tm->ms = (int) timeB.millitm;
17162
17163 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17164     tm->sec = (long) time(NULL);
17165     tm->ms = 0;
17166 #endif
17167 #endif
17168 }
17169
17170 /* Return the difference in milliseconds between two
17171    time marks.  We assume the difference will fit in a long!
17172 */
17173 long
17174 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17175 {
17176     return 1000L*(tm2->sec - tm1->sec) +
17177            (long) (tm2->ms - tm1->ms);
17178 }
17179
17180
17181 /*
17182  * Code to manage the game clocks.
17183  *
17184  * In tournament play, black starts the clock and then white makes a move.
17185  * We give the human user a slight advantage if he is playing white---the
17186  * clocks don't run until he makes his first move, so it takes zero time.
17187  * Also, we don't account for network lag, so we could get out of sync
17188  * with GNU Chess's clock -- but then, referees are always right.
17189  */
17190
17191 static TimeMark tickStartTM;
17192 static long intendedTickLength;
17193
17194 long
17195 NextTickLength (long timeRemaining)
17196 {
17197     long nominalTickLength, nextTickLength;
17198
17199     if (timeRemaining > 0L && timeRemaining <= 10000L)
17200       nominalTickLength = 100L;
17201     else
17202       nominalTickLength = 1000L;
17203     nextTickLength = timeRemaining % nominalTickLength;
17204     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17205
17206     return nextTickLength;
17207 }
17208
17209 /* Adjust clock one minute up or down */
17210 void
17211 AdjustClock (Boolean which, int dir)
17212 {
17213     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17214     if(which) blackTimeRemaining += 60000*dir;
17215     else      whiteTimeRemaining += 60000*dir;
17216     DisplayBothClocks();
17217     adjustedClock = TRUE;
17218 }
17219
17220 /* Stop clocks and reset to a fresh time control */
17221 void
17222 ResetClocks ()
17223 {
17224     (void) StopClockTimer();
17225     if (appData.icsActive) {
17226         whiteTimeRemaining = blackTimeRemaining = 0;
17227     } else if (searchTime) {
17228         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17229         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17230     } else { /* [HGM] correct new time quote for time odds */
17231         whiteTC = blackTC = fullTimeControlString;
17232         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17233         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17234     }
17235     if (whiteFlag || blackFlag) {
17236         DisplayTitle("");
17237         whiteFlag = blackFlag = FALSE;
17238     }
17239     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17240     DisplayBothClocks();
17241     adjustedClock = FALSE;
17242 }
17243
17244 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17245
17246 /* Decrement running clock by amount of time that has passed */
17247 void
17248 DecrementClocks ()
17249 {
17250     long timeRemaining;
17251     long lastTickLength, fudge;
17252     TimeMark now;
17253
17254     if (!appData.clockMode) return;
17255     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17256
17257     GetTimeMark(&now);
17258
17259     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17260
17261     /* Fudge if we woke up a little too soon */
17262     fudge = intendedTickLength - lastTickLength;
17263     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17264
17265     if (WhiteOnMove(forwardMostMove)) {
17266         if(whiteNPS >= 0) lastTickLength = 0;
17267         timeRemaining = whiteTimeRemaining -= lastTickLength;
17268         if(timeRemaining < 0 && !appData.icsActive) {
17269             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17270             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17271                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17272                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17273             }
17274         }
17275         DisplayWhiteClock(whiteTimeRemaining - fudge,
17276                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17277     } else {
17278         if(blackNPS >= 0) lastTickLength = 0;
17279         timeRemaining = blackTimeRemaining -= lastTickLength;
17280         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17281             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17282             if(suddenDeath) {
17283                 blackStartMove = forwardMostMove;
17284                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17285             }
17286         }
17287         DisplayBlackClock(blackTimeRemaining - fudge,
17288                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17289     }
17290     if (CheckFlags()) return;
17291
17292     if(twoBoards) { // count down secondary board's clocks as well
17293         activePartnerTime -= lastTickLength;
17294         partnerUp = 1;
17295         if(activePartner == 'W')
17296             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17297         else
17298             DisplayBlackClock(activePartnerTime, TRUE);
17299         partnerUp = 0;
17300     }
17301
17302     tickStartTM = now;
17303     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17304     StartClockTimer(intendedTickLength);
17305
17306     /* if the time remaining has fallen below the alarm threshold, sound the
17307      * alarm. if the alarm has sounded and (due to a takeback or time control
17308      * with increment) the time remaining has increased to a level above the
17309      * threshold, reset the alarm so it can sound again.
17310      */
17311
17312     if (appData.icsActive && appData.icsAlarm) {
17313
17314         /* make sure we are dealing with the user's clock */
17315         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17316                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17317            )) return;
17318
17319         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17320             alarmSounded = FALSE;
17321         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17322             PlayAlarmSound();
17323             alarmSounded = TRUE;
17324         }
17325     }
17326 }
17327
17328
17329 /* A player has just moved, so stop the previously running
17330    clock and (if in clock mode) start the other one.
17331    We redisplay both clocks in case we're in ICS mode, because
17332    ICS gives us an update to both clocks after every move.
17333    Note that this routine is called *after* forwardMostMove
17334    is updated, so the last fractional tick must be subtracted
17335    from the color that is *not* on move now.
17336 */
17337 void
17338 SwitchClocks (int newMoveNr)
17339 {
17340     long lastTickLength;
17341     TimeMark now;
17342     int flagged = FALSE;
17343
17344     GetTimeMark(&now);
17345
17346     if (StopClockTimer() && appData.clockMode) {
17347         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17348         if (!WhiteOnMove(forwardMostMove)) {
17349             if(blackNPS >= 0) lastTickLength = 0;
17350             blackTimeRemaining -= lastTickLength;
17351            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17352 //         if(pvInfoList[forwardMostMove].time == -1)
17353                  pvInfoList[forwardMostMove].time =               // use GUI time
17354                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17355         } else {
17356            if(whiteNPS >= 0) lastTickLength = 0;
17357            whiteTimeRemaining -= lastTickLength;
17358            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17359 //         if(pvInfoList[forwardMostMove].time == -1)
17360                  pvInfoList[forwardMostMove].time =
17361                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17362         }
17363         flagged = CheckFlags();
17364     }
17365     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17366     CheckTimeControl();
17367
17368     if (flagged || !appData.clockMode) return;
17369
17370     switch (gameMode) {
17371       case MachinePlaysBlack:
17372       case MachinePlaysWhite:
17373       case BeginningOfGame:
17374         if (pausing) return;
17375         break;
17376
17377       case EditGame:
17378       case PlayFromGameFile:
17379       case IcsExamining:
17380         return;
17381
17382       default:
17383         break;
17384     }
17385
17386     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17387         if(WhiteOnMove(forwardMostMove))
17388              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17389         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17390     }
17391
17392     tickStartTM = now;
17393     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17394       whiteTimeRemaining : blackTimeRemaining);
17395     StartClockTimer(intendedTickLength);
17396 }
17397
17398
17399 /* Stop both clocks */
17400 void
17401 StopClocks ()
17402 {
17403     long lastTickLength;
17404     TimeMark now;
17405
17406     if (!StopClockTimer()) return;
17407     if (!appData.clockMode) return;
17408
17409     GetTimeMark(&now);
17410
17411     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17412     if (WhiteOnMove(forwardMostMove)) {
17413         if(whiteNPS >= 0) lastTickLength = 0;
17414         whiteTimeRemaining -= lastTickLength;
17415         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17416     } else {
17417         if(blackNPS >= 0) lastTickLength = 0;
17418         blackTimeRemaining -= lastTickLength;
17419         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17420     }
17421     CheckFlags();
17422 }
17423
17424 /* Start clock of player on move.  Time may have been reset, so
17425    if clock is already running, stop and restart it. */
17426 void
17427 StartClocks ()
17428 {
17429     (void) StopClockTimer(); /* in case it was running already */
17430     DisplayBothClocks();
17431     if (CheckFlags()) return;
17432
17433     if (!appData.clockMode) return;
17434     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17435
17436     GetTimeMark(&tickStartTM);
17437     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17438       whiteTimeRemaining : blackTimeRemaining);
17439
17440    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17441     whiteNPS = blackNPS = -1;
17442     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17443        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17444         whiteNPS = first.nps;
17445     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17446        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17447         blackNPS = first.nps;
17448     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17449         whiteNPS = second.nps;
17450     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17451         blackNPS = second.nps;
17452     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17453
17454     StartClockTimer(intendedTickLength);
17455 }
17456
17457 char *
17458 TimeString (long ms)
17459 {
17460     long second, minute, hour, day;
17461     char *sign = "";
17462     static char buf[32];
17463
17464     if (ms > 0 && ms <= 9900) {
17465       /* convert milliseconds to tenths, rounding up */
17466       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17467
17468       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17469       return buf;
17470     }
17471
17472     /* convert milliseconds to seconds, rounding up */
17473     /* use floating point to avoid strangeness of integer division
17474        with negative dividends on many machines */
17475     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17476
17477     if (second < 0) {
17478         sign = "-";
17479         second = -second;
17480     }
17481
17482     day = second / (60 * 60 * 24);
17483     second = second % (60 * 60 * 24);
17484     hour = second / (60 * 60);
17485     second = second % (60 * 60);
17486     minute = second / 60;
17487     second = second % 60;
17488
17489     if (day > 0)
17490       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17491               sign, day, hour, minute, second);
17492     else if (hour > 0)
17493       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17494     else
17495       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17496
17497     return buf;
17498 }
17499
17500
17501 /*
17502  * This is necessary because some C libraries aren't ANSI C compliant yet.
17503  */
17504 char *
17505 StrStr (char *string, char *match)
17506 {
17507     int i, length;
17508
17509     length = strlen(match);
17510
17511     for (i = strlen(string) - length; i >= 0; i--, string++)
17512       if (!strncmp(match, string, length))
17513         return string;
17514
17515     return NULL;
17516 }
17517
17518 char *
17519 StrCaseStr (char *string, char *match)
17520 {
17521     int i, j, length;
17522
17523     length = strlen(match);
17524
17525     for (i = strlen(string) - length; i >= 0; i--, string++) {
17526         for (j = 0; j < length; j++) {
17527             if (ToLower(match[j]) != ToLower(string[j]))
17528               break;
17529         }
17530         if (j == length) return string;
17531     }
17532
17533     return NULL;
17534 }
17535
17536 #ifndef _amigados
17537 int
17538 StrCaseCmp (char *s1, char *s2)
17539 {
17540     char c1, c2;
17541
17542     for (;;) {
17543         c1 = ToLower(*s1++);
17544         c2 = ToLower(*s2++);
17545         if (c1 > c2) return 1;
17546         if (c1 < c2) return -1;
17547         if (c1 == NULLCHAR) return 0;
17548     }
17549 }
17550
17551
17552 int
17553 ToLower (int c)
17554 {
17555     return isupper(c) ? tolower(c) : c;
17556 }
17557
17558
17559 int
17560 ToUpper (int c)
17561 {
17562     return islower(c) ? toupper(c) : c;
17563 }
17564 #endif /* !_amigados    */
17565
17566 char *
17567 StrSave (char *s)
17568 {
17569   char *ret;
17570
17571   if ((ret = (char *) malloc(strlen(s) + 1)))
17572     {
17573       safeStrCpy(ret, s, strlen(s)+1);
17574     }
17575   return ret;
17576 }
17577
17578 char *
17579 StrSavePtr (char *s, char **savePtr)
17580 {
17581     if (*savePtr) {
17582         free(*savePtr);
17583     }
17584     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17585       safeStrCpy(*savePtr, s, strlen(s)+1);
17586     }
17587     return(*savePtr);
17588 }
17589
17590 char *
17591 PGNDate ()
17592 {
17593     time_t clock;
17594     struct tm *tm;
17595     char buf[MSG_SIZ];
17596
17597     clock = time((time_t *)NULL);
17598     tm = localtime(&clock);
17599     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17600             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17601     return StrSave(buf);
17602 }
17603
17604
17605 char *
17606 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17607 {
17608     int i, j, fromX, fromY, toX, toY;
17609     int whiteToPlay;
17610     char buf[MSG_SIZ];
17611     char *p, *q;
17612     int emptycount;
17613     ChessSquare piece;
17614
17615     whiteToPlay = (gameMode == EditPosition) ?
17616       !blackPlaysFirst : (move % 2 == 0);
17617     p = buf;
17618
17619     /* Piece placement data */
17620     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17621         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17622         emptycount = 0;
17623         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17624             if (boards[move][i][j] == EmptySquare) {
17625                 emptycount++;
17626             } else { ChessSquare piece = boards[move][i][j];
17627                 if (emptycount > 0) {
17628                     if(emptycount<10) /* [HGM] can be >= 10 */
17629                         *p++ = '0' + emptycount;
17630                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17631                     emptycount = 0;
17632                 }
17633                 if(PieceToChar(piece) == '+') {
17634                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17635                     *p++ = '+';
17636                     piece = (ChessSquare)(DEMOTED piece);
17637                 }
17638                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17639                 if(p[-1] == '~') {
17640                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17641                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17642                     *p++ = '~';
17643                 }
17644             }
17645         }
17646         if (emptycount > 0) {
17647             if(emptycount<10) /* [HGM] can be >= 10 */
17648                 *p++ = '0' + emptycount;
17649             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17650             emptycount = 0;
17651         }
17652         *p++ = '/';
17653     }
17654     *(p - 1) = ' ';
17655
17656     /* [HGM] print Crazyhouse or Shogi holdings */
17657     if( gameInfo.holdingsWidth ) {
17658         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17659         q = p;
17660         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17661             piece = boards[move][i][BOARD_WIDTH-1];
17662             if( piece != EmptySquare )
17663               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17664                   *p++ = PieceToChar(piece);
17665         }
17666         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17667             piece = boards[move][BOARD_HEIGHT-i-1][0];
17668             if( piece != EmptySquare )
17669               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17670                   *p++ = PieceToChar(piece);
17671         }
17672
17673         if( q == p ) *p++ = '-';
17674         *p++ = ']';
17675         *p++ = ' ';
17676     }
17677
17678     /* Active color */
17679     *p++ = whiteToPlay ? 'w' : 'b';
17680     *p++ = ' ';
17681
17682   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17683     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17684   } else {
17685   if(nrCastlingRights) {
17686      q = p;
17687      if(appData.fischerCastling) {
17688        /* [HGM] write directly from rights */
17689            if(boards[move][CASTLING][2] != NoRights &&
17690               boards[move][CASTLING][0] != NoRights   )
17691                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17692            if(boards[move][CASTLING][2] != NoRights &&
17693               boards[move][CASTLING][1] != NoRights   )
17694                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17695            if(boards[move][CASTLING][5] != NoRights &&
17696               boards[move][CASTLING][3] != NoRights   )
17697                 *p++ = boards[move][CASTLING][3] + AAA;
17698            if(boards[move][CASTLING][5] != NoRights &&
17699               boards[move][CASTLING][4] != NoRights   )
17700                 *p++ = boards[move][CASTLING][4] + AAA;
17701      } else {
17702
17703         /* [HGM] write true castling rights */
17704         if( nrCastlingRights == 6 ) {
17705             int q, k=0;
17706             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17707                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17708             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17709                  boards[move][CASTLING][2] != NoRights  );
17710             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17711                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17712                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17713                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17714                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17715             }
17716             if(q) *p++ = 'Q';
17717             k = 0;
17718             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17719                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17720             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17721                  boards[move][CASTLING][5] != NoRights  );
17722             if(gameInfo.variant == VariantSChess) {
17723                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17724                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17725                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17726                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17727             }
17728             if(q) *p++ = 'q';
17729         }
17730      }
17731      if (q == p) *p++ = '-'; /* No castling rights */
17732      *p++ = ' ';
17733   }
17734
17735   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17736      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17737      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17738     /* En passant target square */
17739     if (move > backwardMostMove) {
17740         fromX = moveList[move - 1][0] - AAA;
17741         fromY = moveList[move - 1][1] - ONE;
17742         toX = moveList[move - 1][2] - AAA;
17743         toY = moveList[move - 1][3] - ONE;
17744         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17745             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17746             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17747             fromX == toX) {
17748             /* 2-square pawn move just happened */
17749             *p++ = toX + AAA;
17750             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17751         } else {
17752             *p++ = '-';
17753         }
17754     } else if(move == backwardMostMove) {
17755         // [HGM] perhaps we should always do it like this, and forget the above?
17756         if((signed char)boards[move][EP_STATUS] >= 0) {
17757             *p++ = boards[move][EP_STATUS] + AAA;
17758             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17759         } else {
17760             *p++ = '-';
17761         }
17762     } else {
17763         *p++ = '-';
17764     }
17765     *p++ = ' ';
17766   }
17767   }
17768
17769     if(moveCounts)
17770     {   int i = 0, j=move;
17771
17772         /* [HGM] find reversible plies */
17773         if (appData.debugMode) { int k;
17774             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17775             for(k=backwardMostMove; k<=forwardMostMove; k++)
17776                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17777
17778         }
17779
17780         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17781         if( j == backwardMostMove ) i += initialRulePlies;
17782         sprintf(p, "%d ", i);
17783         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17784
17785         /* Fullmove number */
17786         sprintf(p, "%d", (move / 2) + 1);
17787     } else *--p = NULLCHAR;
17788
17789     return StrSave(buf);
17790 }
17791
17792 Boolean
17793 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17794 {
17795     int i, j, k, w=0, subst=0, shuffle=0;
17796     char *p, c;
17797     int emptycount, virgin[BOARD_FILES];
17798     ChessSquare piece;
17799
17800     p = fen;
17801
17802     /* Piece placement data */
17803     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17804         j = 0;
17805         for (;;) {
17806             if (*p == '/' || *p == ' ' || *p == '[' ) {
17807                 if(j > w) w = j;
17808                 emptycount = gameInfo.boardWidth - j;
17809                 while (emptycount--)
17810                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17811                 if (*p == '/') p++;
17812                 else if(autoSize) { // we stumbled unexpectedly into end of board
17813                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17814                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17815                     }
17816                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17817                 }
17818                 break;
17819 #if(BOARD_FILES >= 10)*0
17820             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17821                 p++; emptycount=10;
17822                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17823                 while (emptycount--)
17824                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17825 #endif
17826             } else if (*p == '*') {
17827                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17828             } else if (isdigit(*p)) {
17829                 emptycount = *p++ - '0';
17830                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17831                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17832                 while (emptycount--)
17833                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17834             } else if (*p == '<') {
17835                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17836                 else if (i != 0 || !shuffle) return FALSE;
17837                 p++;
17838             } else if (shuffle && *p == '>') {
17839                 p++; // for now ignore closing shuffle range, and assume rank-end
17840             } else if (*p == '?') {
17841                 if (j >= gameInfo.boardWidth) return FALSE;
17842                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17843                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17844             } else if (*p == '+' || isalpha(*p)) {
17845                 if (j >= gameInfo.boardWidth) return FALSE;
17846                 if(*p=='+') {
17847                     piece = CharToPiece(*++p);
17848                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17849                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17850                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17851                 } else piece = CharToPiece(*p++);
17852
17853                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17854                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17855                     piece = (ChessSquare) (PROMOTED piece);
17856                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17857                     p++;
17858                 }
17859                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17860             } else {
17861                 return FALSE;
17862             }
17863         }
17864     }
17865     while (*p == '/' || *p == ' ') p++;
17866
17867     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17868
17869     /* [HGM] by default clear Crazyhouse holdings, if present */
17870     if(gameInfo.holdingsWidth) {
17871        for(i=0; i<BOARD_HEIGHT; i++) {
17872            board[i][0]             = EmptySquare; /* black holdings */
17873            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17874            board[i][1]             = (ChessSquare) 0; /* black counts */
17875            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17876        }
17877     }
17878
17879     /* [HGM] look for Crazyhouse holdings here */
17880     while(*p==' ') p++;
17881     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17882         int swap=0, wcnt=0, bcnt=0;
17883         if(*p == '[') p++;
17884         if(*p == '<') swap++, p++;
17885         if(*p == '-' ) p++; /* empty holdings */ else {
17886             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17887             /* if we would allow FEN reading to set board size, we would   */
17888             /* have to add holdings and shift the board read so far here   */
17889             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17890                 p++;
17891                 if((int) piece >= (int) BlackPawn ) {
17892                     i = (int)piece - (int)BlackPawn;
17893                     i = PieceToNumber((ChessSquare)i);
17894                     if( i >= gameInfo.holdingsSize ) return FALSE;
17895                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17896                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17897                     bcnt++;
17898                 } else {
17899                     i = (int)piece - (int)WhitePawn;
17900                     i = PieceToNumber((ChessSquare)i);
17901                     if( i >= gameInfo.holdingsSize ) return FALSE;
17902                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17903                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17904                     wcnt++;
17905                 }
17906             }
17907             if(subst) { // substitute back-rank question marks by holdings pieces
17908                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17909                     int k, m, n = bcnt + 1;
17910                     if(board[0][j] == ClearBoard) {
17911                         if(!wcnt) return FALSE;
17912                         n = rand() % wcnt;
17913                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17914                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17915                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17916                             break;
17917                         }
17918                     }
17919                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17920                         if(!bcnt) return FALSE;
17921                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17922                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17923                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17924                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17925                             break;
17926                         }
17927                     }
17928                 }
17929                 subst = 0;
17930             }
17931         }
17932         if(*p == ']') p++;
17933     }
17934
17935     if(subst) return FALSE; // substitution requested, but no holdings
17936
17937     while(*p == ' ') p++;
17938
17939     /* Active color */
17940     c = *p++;
17941     if(appData.colorNickNames) {
17942       if( c == appData.colorNickNames[0] ) c = 'w'; else
17943       if( c == appData.colorNickNames[1] ) c = 'b';
17944     }
17945     switch (c) {
17946       case 'w':
17947         *blackPlaysFirst = FALSE;
17948         break;
17949       case 'b':
17950         *blackPlaysFirst = TRUE;
17951         break;
17952       default:
17953         return FALSE;
17954     }
17955
17956     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17957     /* return the extra info in global variiables             */
17958
17959     /* set defaults in case FEN is incomplete */
17960     board[EP_STATUS] = EP_UNKNOWN;
17961     for(i=0; i<nrCastlingRights; i++ ) {
17962         board[CASTLING][i] =
17963             appData.fischerCastling ? NoRights : initialRights[i];
17964     }   /* assume possible unless obviously impossible */
17965     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17966     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17967     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17968                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17969     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17970     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17971     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17972                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17973     FENrulePlies = 0;
17974
17975     while(*p==' ') p++;
17976     if(nrCastlingRights) {
17977       int fischer = 0;
17978       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17979       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17980           /* castling indicator present, so default becomes no castlings */
17981           for(i=0; i<nrCastlingRights; i++ ) {
17982                  board[CASTLING][i] = NoRights;
17983           }
17984       }
17985       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17986              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17987              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17988              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17989         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17990
17991         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17992             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17993             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17994         }
17995         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17996             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17997         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17998                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17999         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18000                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18001         switch(c) {
18002           case'K':
18003               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18004               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18005               board[CASTLING][2] = whiteKingFile;
18006               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18007               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18008               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18009               break;
18010           case'Q':
18011               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18012               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18013               board[CASTLING][2] = whiteKingFile;
18014               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18015               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18016               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18017               break;
18018           case'k':
18019               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18020               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18021               board[CASTLING][5] = blackKingFile;
18022               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18023               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18024               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18025               break;
18026           case'q':
18027               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18028               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18029               board[CASTLING][5] = blackKingFile;
18030               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18031               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18032               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18033           case '-':
18034               break;
18035           default: /* FRC castlings */
18036               if(c >= 'a') { /* black rights */
18037                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18038                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18039                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18040                   if(i == BOARD_RGHT) break;
18041                   board[CASTLING][5] = i;
18042                   c -= AAA;
18043                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18044                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18045                   if(c > i)
18046                       board[CASTLING][3] = c;
18047                   else
18048                       board[CASTLING][4] = c;
18049               } else { /* white rights */
18050                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18051                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18052                     if(board[0][i] == WhiteKing) break;
18053                   if(i == BOARD_RGHT) break;
18054                   board[CASTLING][2] = i;
18055                   c -= AAA - 'a' + 'A';
18056                   if(board[0][c] >= WhiteKing) break;
18057                   if(c > i)
18058                       board[CASTLING][0] = c;
18059                   else
18060                       board[CASTLING][1] = c;
18061               }
18062         }
18063       }
18064       for(i=0; i<nrCastlingRights; i++)
18065         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18066       if(gameInfo.variant == VariantSChess)
18067         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18068       if(fischer && shuffle) appData.fischerCastling = TRUE;
18069     if (appData.debugMode) {
18070         fprintf(debugFP, "FEN castling rights:");
18071         for(i=0; i<nrCastlingRights; i++)
18072         fprintf(debugFP, " %d", board[CASTLING][i]);
18073         fprintf(debugFP, "\n");
18074     }
18075
18076       while(*p==' ') p++;
18077     }
18078
18079     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18080
18081     /* read e.p. field in games that know e.p. capture */
18082     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18083        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18084        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18085       if(*p=='-') {
18086         p++; board[EP_STATUS] = EP_NONE;
18087       } else {
18088          char c = *p++ - AAA;
18089
18090          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18091          if(*p >= '0' && *p <='9') p++;
18092          board[EP_STATUS] = c;
18093       }
18094     }
18095
18096
18097     if(sscanf(p, "%d", &i) == 1) {
18098         FENrulePlies = i; /* 50-move ply counter */
18099         /* (The move number is still ignored)    */
18100     }
18101
18102     return TRUE;
18103 }
18104
18105 void
18106 EditPositionPasteFEN (char *fen)
18107 {
18108   if (fen != NULL) {
18109     Board initial_position;
18110
18111     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18112       DisplayError(_("Bad FEN position in clipboard"), 0);
18113       return ;
18114     } else {
18115       int savedBlackPlaysFirst = blackPlaysFirst;
18116       EditPositionEvent();
18117       blackPlaysFirst = savedBlackPlaysFirst;
18118       CopyBoard(boards[0], initial_position);
18119       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18120       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18121       DisplayBothClocks();
18122       DrawPosition(FALSE, boards[currentMove]);
18123     }
18124   }
18125 }
18126
18127 static char cseq[12] = "\\   ";
18128
18129 Boolean
18130 set_cont_sequence (char *new_seq)
18131 {
18132     int len;
18133     Boolean ret;
18134
18135     // handle bad attempts to set the sequence
18136         if (!new_seq)
18137                 return 0; // acceptable error - no debug
18138
18139     len = strlen(new_seq);
18140     ret = (len > 0) && (len < sizeof(cseq));
18141     if (ret)
18142       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18143     else if (appData.debugMode)
18144       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18145     return ret;
18146 }
18147
18148 /*
18149     reformat a source message so words don't cross the width boundary.  internal
18150     newlines are not removed.  returns the wrapped size (no null character unless
18151     included in source message).  If dest is NULL, only calculate the size required
18152     for the dest buffer.  lp argument indicats line position upon entry, and it's
18153     passed back upon exit.
18154 */
18155 int
18156 wrap (char *dest, char *src, int count, int width, int *lp)
18157 {
18158     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18159
18160     cseq_len = strlen(cseq);
18161     old_line = line = *lp;
18162     ansi = len = clen = 0;
18163
18164     for (i=0; i < count; i++)
18165     {
18166         if (src[i] == '\033')
18167             ansi = 1;
18168
18169         // if we hit the width, back up
18170         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18171         {
18172             // store i & len in case the word is too long
18173             old_i = i, old_len = len;
18174
18175             // find the end of the last word
18176             while (i && src[i] != ' ' && src[i] != '\n')
18177             {
18178                 i--;
18179                 len--;
18180             }
18181
18182             // word too long?  restore i & len before splitting it
18183             if ((old_i-i+clen) >= width)
18184             {
18185                 i = old_i;
18186                 len = old_len;
18187             }
18188
18189             // extra space?
18190             if (i && src[i-1] == ' ')
18191                 len--;
18192
18193             if (src[i] != ' ' && src[i] != '\n')
18194             {
18195                 i--;
18196                 if (len)
18197                     len--;
18198             }
18199
18200             // now append the newline and continuation sequence
18201             if (dest)
18202                 dest[len] = '\n';
18203             len++;
18204             if (dest)
18205                 strncpy(dest+len, cseq, cseq_len);
18206             len += cseq_len;
18207             line = cseq_len;
18208             clen = cseq_len;
18209             continue;
18210         }
18211
18212         if (dest)
18213             dest[len] = src[i];
18214         len++;
18215         if (!ansi)
18216             line++;
18217         if (src[i] == '\n')
18218             line = 0;
18219         if (src[i] == 'm')
18220             ansi = 0;
18221     }
18222     if (dest && appData.debugMode)
18223     {
18224         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18225             count, width, line, len, *lp);
18226         show_bytes(debugFP, src, count);
18227         fprintf(debugFP, "\ndest: ");
18228         show_bytes(debugFP, dest, len);
18229         fprintf(debugFP, "\n");
18230     }
18231     *lp = dest ? line : old_line;
18232
18233     return len;
18234 }
18235
18236 // [HGM] vari: routines for shelving variations
18237 Boolean modeRestore = FALSE;
18238
18239 void
18240 PushInner (int firstMove, int lastMove)
18241 {
18242         int i, j, nrMoves = lastMove - firstMove;
18243
18244         // push current tail of game on stack
18245         savedResult[storedGames] = gameInfo.result;
18246         savedDetails[storedGames] = gameInfo.resultDetails;
18247         gameInfo.resultDetails = NULL;
18248         savedFirst[storedGames] = firstMove;
18249         savedLast [storedGames] = lastMove;
18250         savedFramePtr[storedGames] = framePtr;
18251         framePtr -= nrMoves; // reserve space for the boards
18252         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18253             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18254             for(j=0; j<MOVE_LEN; j++)
18255                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18256             for(j=0; j<2*MOVE_LEN; j++)
18257                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18258             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18259             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18260             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18261             pvInfoList[firstMove+i-1].depth = 0;
18262             commentList[framePtr+i] = commentList[firstMove+i];
18263             commentList[firstMove+i] = NULL;
18264         }
18265
18266         storedGames++;
18267         forwardMostMove = firstMove; // truncate game so we can start variation
18268 }
18269
18270 void
18271 PushTail (int firstMove, int lastMove)
18272 {
18273         if(appData.icsActive) { // only in local mode
18274                 forwardMostMove = currentMove; // mimic old ICS behavior
18275                 return;
18276         }
18277         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18278
18279         PushInner(firstMove, lastMove);
18280         if(storedGames == 1) GreyRevert(FALSE);
18281         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18282 }
18283
18284 void
18285 PopInner (Boolean annotate)
18286 {
18287         int i, j, nrMoves;
18288         char buf[8000], moveBuf[20];
18289
18290         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18291         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18292         nrMoves = savedLast[storedGames] - currentMove;
18293         if(annotate) {
18294                 int cnt = 10;
18295                 if(!WhiteOnMove(currentMove))
18296                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18297                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18298                 for(i=currentMove; i<forwardMostMove; i++) {
18299                         if(WhiteOnMove(i))
18300                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18301                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18302                         strcat(buf, moveBuf);
18303                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18304                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18305                 }
18306                 strcat(buf, ")");
18307         }
18308         for(i=1; i<=nrMoves; i++) { // copy last variation back
18309             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18310             for(j=0; j<MOVE_LEN; j++)
18311                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18312             for(j=0; j<2*MOVE_LEN; j++)
18313                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18314             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18315             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18316             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18317             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18318             commentList[currentMove+i] = commentList[framePtr+i];
18319             commentList[framePtr+i] = NULL;
18320         }
18321         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18322         framePtr = savedFramePtr[storedGames];
18323         gameInfo.result = savedResult[storedGames];
18324         if(gameInfo.resultDetails != NULL) {
18325             free(gameInfo.resultDetails);
18326       }
18327         gameInfo.resultDetails = savedDetails[storedGames];
18328         forwardMostMove = currentMove + nrMoves;
18329 }
18330
18331 Boolean
18332 PopTail (Boolean annotate)
18333 {
18334         if(appData.icsActive) return FALSE; // only in local mode
18335         if(!storedGames) return FALSE; // sanity
18336         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18337
18338         PopInner(annotate);
18339         if(currentMove < forwardMostMove) ForwardEvent(); else
18340         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18341
18342         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18343         return TRUE;
18344 }
18345
18346 void
18347 CleanupTail ()
18348 {       // remove all shelved variations
18349         int i;
18350         for(i=0; i<storedGames; i++) {
18351             if(savedDetails[i])
18352                 free(savedDetails[i]);
18353             savedDetails[i] = NULL;
18354         }
18355         for(i=framePtr; i<MAX_MOVES; i++) {
18356                 if(commentList[i]) free(commentList[i]);
18357                 commentList[i] = NULL;
18358         }
18359         framePtr = MAX_MOVES-1;
18360         storedGames = 0;
18361 }
18362
18363 void
18364 LoadVariation (int index, char *text)
18365 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18366         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18367         int level = 0, move;
18368
18369         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18370         // first find outermost bracketing variation
18371         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18372             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18373                 if(*p == '{') wait = '}'; else
18374                 if(*p == '[') wait = ']'; else
18375                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18376                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18377             }
18378             if(*p == wait) wait = NULLCHAR; // closing ]} found
18379             p++;
18380         }
18381         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18382         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18383         end[1] = NULLCHAR; // clip off comment beyond variation
18384         ToNrEvent(currentMove-1);
18385         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18386         // kludge: use ParsePV() to append variation to game
18387         move = currentMove;
18388         ParsePV(start, TRUE, TRUE);
18389         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18390         ClearPremoveHighlights();
18391         CommentPopDown();
18392         ToNrEvent(currentMove+1);
18393 }
18394
18395 void
18396 LoadTheme ()
18397 {
18398     char *p, *q, buf[MSG_SIZ];
18399     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18400         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18401         ParseArgsFromString(buf);
18402         ActivateTheme(TRUE); // also redo colors
18403         return;
18404     }
18405     p = nickName;
18406     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18407     {
18408         int len;
18409         q = appData.themeNames;
18410         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18411       if(appData.useBitmaps) {
18412         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18413                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18414                 appData.liteBackTextureMode,
18415                 appData.darkBackTextureMode );
18416       } else {
18417         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18418                 Col2Text(2),   // lightSquareColor
18419                 Col2Text(3) ); // darkSquareColor
18420       }
18421       if(appData.useBorder) {
18422         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18423                 appData.border);
18424       } else {
18425         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18426       }
18427       if(appData.useFont) {
18428         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18429                 appData.renderPiecesWithFont,
18430                 appData.fontToPieceTable,
18431                 Col2Text(9),    // appData.fontBackColorWhite
18432                 Col2Text(10) ); // appData.fontForeColorBlack
18433       } else {
18434         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18435                 appData.pieceDirectory);
18436         if(!appData.pieceDirectory[0])
18437           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18438                 Col2Text(0),   // whitePieceColor
18439                 Col2Text(1) ); // blackPieceColor
18440       }
18441       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18442                 Col2Text(4),   // highlightSquareColor
18443                 Col2Text(5) ); // premoveHighlightColor
18444         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18445         if(insert != q) insert[-1] = NULLCHAR;
18446         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18447         if(q)   free(q);
18448     }
18449     ActivateTheme(FALSE);
18450 }