9f90dad56ff3eadfaee77c7faa836231f17c381a
[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 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 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225 void ToggleSecond P((void));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
274
275 /* States for ics_getting_history */
276 #define H_FALSE 0
277 #define H_REQUESTED 1
278 #define H_GOT_REQ_HEADER 2
279 #define H_GOT_UNREQ_HEADER 3
280 #define H_GETTING_MOVES 4
281 #define H_GOT_UNWANTED_HEADER 5
282
283 /* whosays values for GameEnds */
284 #define GE_ICS 0
285 #define GE_ENGINE 1
286 #define GE_PLAYER 2
287 #define GE_FILE 3
288 #define GE_XBOARD 4
289 #define GE_ENGINE1 5
290 #define GE_ENGINE2 6
291
292 /* Maximum number of games in a cmail message */
293 #define CMAIL_MAX_GAMES 20
294
295 /* Different types of move when calling RegisterMove */
296 #define CMAIL_MOVE   0
297 #define CMAIL_RESIGN 1
298 #define CMAIL_DRAW   2
299 #define CMAIL_ACCEPT 3
300
301 /* Different types of result to remember for each game */
302 #define CMAIL_NOT_RESULT 0
303 #define CMAIL_OLD_RESULT 1
304 #define CMAIL_NEW_RESULT 2
305
306 /* Telnet protocol constants */
307 #define TN_WILL 0373
308 #define TN_WONT 0374
309 #define TN_DO   0375
310 #define TN_DONT 0376
311 #define TN_IAC  0377
312 #define TN_ECHO 0001
313 #define TN_SGA  0003
314 #define TN_PORT 23
315
316 char*
317 safeStrCpy (char *dst, const char *src, size_t count)
318 { // [HGM] made safe
319   int i;
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
325   if(  i == count && dst[count-1] != NULLCHAR)
326     {
327       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
328       if(appData.debugMode)
329       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
330     }
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble (u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags (index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388   case VariantGrand:
389     flags &= ~F_ALL_CASTLE_OK;
390     break;
391   default:
392     break;
393   }
394   return flags;
395 }
396
397 FILE *gameFileFP, *debugFP, *serverFP;
398 char *currentDebugFile; // [HGM] debug split: to remember name
399
400 /*
401     [AS] Note: sometimes, the sscanf() function is used to parse the input
402     into a fixed-size buffer. Because of this, we must be prepared to
403     receive strings as long as the size of the input buffer, which is currently
404     set to 4K for Windows and 8K for the rest.
405     So, we must either allocate sufficiently large buffers here, or
406     reduce the size of the input buffer in the input reading part.
407 */
408
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
412
413 ChessProgramState first, second, pairing;
414
415 /* premove variables */
416 int premoveToX = 0;
417 int premoveToY = 0;
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
421 int gotPremove = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
424
425 char *ics_prefix = "$";
426 enum ICS_TYPE ics_type = ICS_GENERIC;
427
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey, controlKey; // [HGM] set by mouse handler
455
456 int have_sent_ICS_logon = 0;
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
460 Boolean adjustedClock;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0, nextGame = 0, roundNr = 0;
465 Boolean waitingForGame = FALSE;
466 TimeMark programStartTime, pauseStart;
467 char ics_handle[MSG_SIZ];
468 int have_set_title = 0;
469
470 /* animateTraining preserves the state of appData.animate
471  * when Training mode is activated. This allows the
472  * response to be animated when appData.animate == TRUE and
473  * appData.animateDragging == TRUE.
474  */
475 Boolean animateTraining;
476
477 GameInfo gameInfo;
478
479 AppData appData;
480
481 Board boards[MAX_MOVES];
482 /* [HGM] Following 7 needed for accurate legality tests: */
483 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
484 signed char  initialRights[BOARD_FILES];
485 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
486 int   initialRulePlies, FENrulePlies;
487 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 int loadFlag = 0;
489 Boolean shuffleOpenings;
490 int mute; // mute all sounds
491
492 // [HGM] vari: next 12 to save and restore variations
493 #define MAX_VARIATIONS 10
494 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int storedGames = 0;
496 int savedFirst[MAX_VARIATIONS];
497 int savedLast[MAX_VARIATIONS];
498 int savedFramePtr[MAX_VARIATIONS];
499 char *savedDetails[MAX_VARIATIONS];
500 ChessMove savedResult[MAX_VARIATIONS];
501
502 void PushTail P((int firstMove, int lastMove));
503 Boolean PopTail P((Boolean annotate));
504 void PushInner P((int firstMove, int lastMove));
505 void PopInner P((Boolean annotate));
506 void CleanupTail P((void));
507
508 ChessSquare  FIDEArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackBishop, BlackKnight, BlackRook }
513 };
514
515 ChessSquare twoKingsArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackKing, BlackKnight, BlackRook }
520 };
521
522 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
524         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
525     { BlackRook, BlackMan, BlackBishop, BlackQueen,
526         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
527 };
528
529 ChessSquare SpartanArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
533         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
534 };
535
536 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
540         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
541 };
542
543 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
545         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
547         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
548 };
549
550 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
552         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackMan, BlackFerz,
554         BlackKing, BlackMan, BlackKnight, BlackRook }
555 };
556
557
558 #if (BOARD_FILES>=10)
559 ChessSquare ShogiArray[2][BOARD_FILES] = {
560     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
561         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
562     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
563         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
564 };
565
566 ChessSquare XiangqiArray[2][BOARD_FILES] = {
567     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
568         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
569     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
570         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
571 };
572
573 ChessSquare CapablancaArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
575         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
577         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
578 };
579
580 ChessSquare GreatArray[2][BOARD_FILES] = {
581     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
582         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
583     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
584         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
585 };
586
587 ChessSquare JanusArray[2][BOARD_FILES] = {
588     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
589         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
590     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
591         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
592 };
593
594 ChessSquare GrandArray[2][BOARD_FILES] = {
595     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
596         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
597     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
598         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
615         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
617         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating (char *str)
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats ()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit ()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine (ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions (ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
740      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 N_("first"),
742   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
743      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
744 N_("second")
745 };
746
747 void
748 InitEngine (ChessProgramState *cps, int n)
749 {   // [HGM] all engine initialiation put in a function that does one engine
750
751     ClearOptions(cps);
752
753     cps->which = engineNames[n];
754     cps->maybeThinking = FALSE;
755     cps->pr = NoProc;
756     cps->isr = NULL;
757     cps->sendTime = 2;
758     cps->sendDrawOffers = 1;
759
760     cps->program = appData.chessProgram[n];
761     cps->host = appData.host[n];
762     cps->dir = appData.directory[n];
763     cps->initString = appData.engInitString[n];
764     cps->computerString = appData.computerString[n];
765     cps->useSigint  = TRUE;
766     cps->useSigterm = TRUE;
767     cps->reuse = appData.reuse[n];
768     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
769     cps->useSetboard = FALSE;
770     cps->useSAN = FALSE;
771     cps->usePing = FALSE;
772     cps->lastPing = 0;
773     cps->lastPong = 0;
774     cps->usePlayother = FALSE;
775     cps->useColors = TRUE;
776     cps->useUsermove = FALSE;
777     cps->sendICS = FALSE;
778     cps->sendName = appData.icsActive;
779     cps->sdKludge = FALSE;
780     cps->stKludge = FALSE;
781     TidyProgramName(cps->program, cps->host, cps->tidy);
782     cps->matchWins = 0;
783     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
784     cps->analysisSupport = 2; /* detect */
785     cps->analyzing = FALSE;
786     cps->initDone = FALSE;
787
788     /* New features added by Tord: */
789     cps->useFEN960 = FALSE;
790     cps->useOOCastle = TRUE;
791     /* End of new features added by Tord. */
792     cps->fenOverride  = appData.fenOverride[n];
793
794     /* [HGM] time odds: set factor for each machine */
795     cps->timeOdds  = appData.timeOdds[n];
796
797     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
798     cps->accumulateTC = appData.accumulateTC[n];
799     cps->maxNrOfSessions = 1;
800
801     /* [HGM] debug */
802     cps->debug = FALSE;
803
804     cps->supportsNPS = UNKNOWN;
805     cps->memSize = FALSE;
806     cps->maxCores = FALSE;
807     cps->egtFormats[0] = NULLCHAR;
808
809     /* [HGM] options */
810     cps->optionSettings  = appData.engOptions[n];
811
812     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
813     cps->isUCI = appData.isUCI[n]; /* [AS] */
814     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815
816     if (appData.protocolVersion[n] > PROTOVER
817         || appData.protocolVersion[n] < 1)
818       {
819         char buf[MSG_SIZ];
820         int len;
821
822         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
823                        appData.protocolVersion[n]);
824         if( (len >= MSG_SIZ) && appData.debugMode )
825           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826
827         DisplayFatalError(buf, 0, 2);
828       }
829     else
830       {
831         cps->protocolVersion = appData.protocolVersion[n];
832       }
833
834     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
835     ParseFeatures(appData.featureDefaults, cps);
836 }
837
838 ChessProgramState *savCps;
839
840 void
841 LoadEngine ()
842 {
843     int i;
844     if(WaitForEngine(savCps, LoadEngine)) return;
845     CommonEngineInit(); // recalculate time odds
846     if(gameInfo.variant != StringToVariant(appData.variant)) {
847         // we changed variant when loading the engine; this forces us to reset
848         Reset(TRUE, savCps != &first);
849         EditGameEvent(); // for consistency with other path, as Reset changes mode
850     }
851     InitChessProgram(savCps, FALSE);
852     SendToProgram("force\n", savCps);
853     DisplayMessage("", "");
854     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
855     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
856     ThawUI();
857     SetGNUMode();
858 }
859
860 void
861 ReplaceEngine (ChessProgramState *cps, int n)
862 {
863     EditGameEvent();
864     UnloadEngine(cps);
865     appData.noChessProgram = FALSE;
866     appData.clockMode = TRUE;
867     InitEngine(cps, n);
868     UpdateLogos(TRUE);
869     if(n) return; // only startup first engine immediately; second can wait
870     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
871     LoadEngine();
872 }
873
874 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
875 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876
877 static char resetOptions[] = 
878         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
879         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
880         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
881         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
882
883 void
884 FloatToFront(char **list, char *engineLine)
885 {
886     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
887     int i=0;
888     if(appData.recentEngines <= 0) return;
889     TidyProgramName(engineLine, "localhost", tidy+1);
890     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
891     strncpy(buf+1, *list, MSG_SIZ-50);
892     if(p = strstr(buf, tidy)) { // tidy name appears in list
893         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
894         while(*p++ = *++q); // squeeze out
895     }
896     strcat(tidy, buf+1); // put list behind tidy name
897     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
898     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
899     ASSIGN(*list, tidy+1);
900 }
901
902 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
903
904 void
905 Load (ChessProgramState *cps, int i)
906 {
907     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
908     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
909         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
910         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
911         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
912         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
913         appData.firstProtocolVersion = PROTOVER;
914         ParseArgsFromString(buf);
915         SwapEngines(i);
916         ReplaceEngine(cps, i);
917         FloatToFront(&appData.recentEngineList, engineLine);
918         return;
919     }
920     p = engineName;
921     while(q = strchr(p, SLASH)) p = q+1;
922     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
923     if(engineDir[0] != NULLCHAR) {
924         ASSIGN(appData.directory[i], engineDir); p = engineName;
925     } else if(p != engineName) { // derive directory from engine path, when not given
926         p[-1] = 0;
927         ASSIGN(appData.directory[i], engineName);
928         p[-1] = SLASH;
929         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
930     } else { ASSIGN(appData.directory[i], "."); }
931     if(params[0]) {
932         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
933         snprintf(command, MSG_SIZ, "%s %s", p, params);
934         p = command;
935     }
936     ASSIGN(appData.chessProgram[i], p);
937     appData.isUCI[i] = isUCI;
938     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
939     appData.hasOwnBookUCI[i] = hasBook;
940     if(!nickName[0]) useNick = FALSE;
941     if(useNick) ASSIGN(appData.pgnName[i], nickName);
942     if(addToList) {
943         int len;
944         char quote;
945         q = firstChessProgramNames;
946         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
947         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
948         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
949                         quote, p, quote, appData.directory[i], 
950                         useNick ? " -fn \"" : "",
951                         useNick ? nickName : "",
952                         useNick ? "\"" : "",
953                         v1 ? " -firstProtocolVersion 1" : "",
954                         hasBook ? "" : " -fNoOwnBookUCI",
955                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
956                         storeVariant ? " -variant " : "",
957                         storeVariant ? VariantName(gameInfo.variant) : "");
958         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
959         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
960         if(insert != q) insert[-1] = NULLCHAR;
961         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
962         if(q)   free(q);
963         FloatToFront(&appData.recentEngineList, buf);
964     }
965     ReplaceEngine(cps, i);
966 }
967
968 void
969 InitTimeControls ()
970 {
971     int matched, min, sec;
972     /*
973      * Parse timeControl resource
974      */
975     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
976                           appData.movesPerSession)) {
977         char buf[MSG_SIZ];
978         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
979         DisplayFatalError(buf, 0, 2);
980     }
981
982     /*
983      * Parse searchTime resource
984      */
985     if (*appData.searchTime != NULLCHAR) {
986         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
987         if (matched == 1) {
988             searchTime = min * 60;
989         } else if (matched == 2) {
990             searchTime = min * 60 + sec;
991         } else {
992             char buf[MSG_SIZ];
993             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
994             DisplayFatalError(buf, 0, 2);
995         }
996     }
997 }
998
999 void
1000 InitBackEnd1 ()
1001 {
1002
1003     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1004     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1005
1006     GetTimeMark(&programStartTime);
1007     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1008     appData.seedBase = random() + (random()<<15);
1009     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1010
1011     ClearProgramStats();
1012     programStats.ok_to_send = 1;
1013     programStats.seen_stat = 0;
1014
1015     /*
1016      * Initialize game list
1017      */
1018     ListNew(&gameList);
1019
1020
1021     /*
1022      * Internet chess server status
1023      */
1024     if (appData.icsActive) {
1025         appData.matchMode = FALSE;
1026         appData.matchGames = 0;
1027 #if ZIPPY
1028         appData.noChessProgram = !appData.zippyPlay;
1029 #else
1030         appData.zippyPlay = FALSE;
1031         appData.zippyTalk = FALSE;
1032         appData.noChessProgram = TRUE;
1033 #endif
1034         if (*appData.icsHelper != NULLCHAR) {
1035             appData.useTelnet = TRUE;
1036             appData.telnetProgram = appData.icsHelper;
1037         }
1038     } else {
1039         appData.zippyTalk = appData.zippyPlay = FALSE;
1040     }
1041
1042     /* [AS] Initialize pv info list [HGM] and game state */
1043     {
1044         int i, j;
1045
1046         for( i=0; i<=framePtr; i++ ) {
1047             pvInfoList[i].depth = -1;
1048             boards[i][EP_STATUS] = EP_NONE;
1049             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1050         }
1051     }
1052
1053     InitTimeControls();
1054
1055     /* [AS] Adjudication threshold */
1056     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1057
1058     InitEngine(&first, 0);
1059     InitEngine(&second, 1);
1060     CommonEngineInit();
1061
1062     pairing.which = "pairing"; // pairing engine
1063     pairing.pr = NoProc;
1064     pairing.isr = NULL;
1065     pairing.program = appData.pairingEngine;
1066     pairing.host = "localhost";
1067     pairing.dir = ".";
1068
1069     if (appData.icsActive) {
1070         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1071     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1072         appData.clockMode = FALSE;
1073         first.sendTime = second.sendTime = 0;
1074     }
1075
1076 #if ZIPPY
1077     /* Override some settings from environment variables, for backward
1078        compatibility.  Unfortunately it's not feasible to have the env
1079        vars just set defaults, at least in xboard.  Ugh.
1080     */
1081     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1082       ZippyInit();
1083     }
1084 #endif
1085
1086     if (!appData.icsActive) {
1087       char buf[MSG_SIZ];
1088       int len;
1089
1090       /* Check for variants that are supported only in ICS mode,
1091          or not at all.  Some that are accepted here nevertheless
1092          have bugs; see comments below.
1093       */
1094       VariantClass variant = StringToVariant(appData.variant);
1095       switch (variant) {
1096       case VariantBughouse:     /* need four players and two boards */
1097       case VariantKriegspiel:   /* need to hide pieces and move details */
1098         /* case VariantFischeRandom: (Fabien: moved below) */
1099         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1100         if( (len >= MSG_SIZ) && appData.debugMode )
1101           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1102
1103         DisplayFatalError(buf, 0, 2);
1104         return;
1105
1106       case VariantUnknown:
1107       case VariantLoadable:
1108       case Variant29:
1109       case Variant30:
1110       case Variant31:
1111       case Variant32:
1112       case Variant33:
1113       case Variant34:
1114       case Variant35:
1115       case Variant36:
1116       default:
1117         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1118         if( (len >= MSG_SIZ) && appData.debugMode )
1119           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1120
1121         DisplayFatalError(buf, 0, 2);
1122         return;
1123
1124       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1125       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1126       case VariantGothic:     /* [HGM] should work */
1127       case VariantCapablanca: /* [HGM] should work */
1128       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1129       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1130       case VariantKnightmate: /* [HGM] should work */
1131       case VariantCylinder:   /* [HGM] untested */
1132       case VariantFalcon:     /* [HGM] untested */
1133       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1134                                  offboard interposition not understood */
1135       case VariantNormal:     /* definitely works! */
1136       case VariantWildCastle: /* pieces not automatically shuffled */
1137       case VariantNoCastle:   /* pieces not automatically shuffled */
1138       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1139       case VariantLosers:     /* should work except for win condition,
1140                                  and doesn't know captures are mandatory */
1141       case VariantSuicide:    /* should work except for win condition,
1142                                  and doesn't know captures are mandatory */
1143       case VariantGiveaway:   /* should work except for win condition,
1144                                  and doesn't know captures are mandatory */
1145       case VariantTwoKings:   /* should work */
1146       case VariantAtomic:     /* should work except for win condition */
1147       case Variant3Check:     /* should work except for win condition */
1148       case VariantShatranj:   /* should work except for all win conditions */
1149       case VariantMakruk:     /* should work except for draw countdown */
1150       case VariantBerolina:   /* might work if TestLegality is off */
1151       case VariantCapaRandom: /* should work */
1152       case VariantJanus:      /* should work */
1153       case VariantSuper:      /* experimental */
1154       case VariantGreat:      /* experimental, requires legality testing to be off */
1155       case VariantSChess:     /* S-Chess, should work */
1156       case VariantGrand:      /* should work */
1157       case VariantSpartan:    /* should work */
1158         break;
1159       }
1160     }
1161
1162 }
1163
1164 int
1165 NextIntegerFromString (char ** str, long * value)
1166 {
1167     int result = -1;
1168     char * s = *str;
1169
1170     while( *s == ' ' || *s == '\t' ) {
1171         s++;
1172     }
1173
1174     *value = 0;
1175
1176     if( *s >= '0' && *s <= '9' ) {
1177         while( *s >= '0' && *s <= '9' ) {
1178             *value = *value * 10 + (*s - '0');
1179             s++;
1180         }
1181
1182         result = 0;
1183     }
1184
1185     *str = s;
1186
1187     return result;
1188 }
1189
1190 int
1191 NextTimeControlFromString (char ** str, long * value)
1192 {
1193     long temp;
1194     int result = NextIntegerFromString( str, &temp );
1195
1196     if( result == 0 ) {
1197         *value = temp * 60; /* Minutes */
1198         if( **str == ':' ) {
1199             (*str)++;
1200             result = NextIntegerFromString( str, &temp );
1201             *value += temp; /* Seconds */
1202         }
1203     }
1204
1205     return result;
1206 }
1207
1208 int
1209 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1210 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1211     int result = -1, type = 0; long temp, temp2;
1212
1213     if(**str != ':') return -1; // old params remain in force!
1214     (*str)++;
1215     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1216     if( NextIntegerFromString( str, &temp ) ) return -1;
1217     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1218
1219     if(**str != '/') {
1220         /* time only: incremental or sudden-death time control */
1221         if(**str == '+') { /* increment follows; read it */
1222             (*str)++;
1223             if(**str == '!') type = *(*str)++; // Bronstein TC
1224             if(result = NextIntegerFromString( str, &temp2)) return -1;
1225             *inc = temp2 * 1000;
1226             if(**str == '.') { // read fraction of increment
1227                 char *start = ++(*str);
1228                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1229                 temp2 *= 1000;
1230                 while(start++ < *str) temp2 /= 10;
1231                 *inc += temp2;
1232             }
1233         } else *inc = 0;
1234         *moves = 0; *tc = temp * 1000; *incType = type;
1235         return 0;
1236     }
1237
1238     (*str)++; /* classical time control */
1239     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1240
1241     if(result == 0) {
1242         *moves = temp;
1243         *tc    = temp2 * 1000;
1244         *inc   = 0;
1245         *incType = type;
1246     }
1247     return result;
1248 }
1249
1250 int
1251 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1252 {   /* [HGM] get time to add from the multi-session time-control string */
1253     int incType, moves=1; /* kludge to force reading of first session */
1254     long time, increment;
1255     char *s = tcString;
1256
1257     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1258     do {
1259         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1260         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1261         if(movenr == -1) return time;    /* last move before new session     */
1262         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1263         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1264         if(!moves) return increment;     /* current session is incremental   */
1265         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1266     } while(movenr >= -1);               /* try again for next session       */
1267
1268     return 0; // no new time quota on this move
1269 }
1270
1271 int
1272 ParseTimeControl (char *tc, float ti, int mps)
1273 {
1274   long tc1;
1275   long tc2;
1276   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1277   int min, sec=0;
1278
1279   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1280   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1281       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1282   if(ti > 0) {
1283
1284     if(mps)
1285       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1286     else 
1287       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1288   } else {
1289     if(mps)
1290       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1291     else 
1292       snprintf(buf, MSG_SIZ, ":%s", mytc);
1293   }
1294   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1295   
1296   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1297     return FALSE;
1298   }
1299
1300   if( *tc == '/' ) {
1301     /* Parse second time control */
1302     tc++;
1303
1304     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1305       return FALSE;
1306     }
1307
1308     if( tc2 == 0 ) {
1309       return FALSE;
1310     }
1311
1312     timeControl_2 = tc2 * 1000;
1313   }
1314   else {
1315     timeControl_2 = 0;
1316   }
1317
1318   if( tc1 == 0 ) {
1319     return FALSE;
1320   }
1321
1322   timeControl = tc1 * 1000;
1323
1324   if (ti >= 0) {
1325     timeIncrement = ti * 1000;  /* convert to ms */
1326     movesPerSession = 0;
1327   } else {
1328     timeIncrement = 0;
1329     movesPerSession = mps;
1330   }
1331   return TRUE;
1332 }
1333
1334 void
1335 InitBackEnd2 ()
1336 {
1337     if (appData.debugMode) {
1338         fprintf(debugFP, "%s\n", programVersion);
1339     }
1340     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1341
1342     set_cont_sequence(appData.wrapContSeq);
1343     if (appData.matchGames > 0) {
1344         appData.matchMode = TRUE;
1345     } else if (appData.matchMode) {
1346         appData.matchGames = 1;
1347     }
1348     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1349         appData.matchGames = appData.sameColorGames;
1350     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1351         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1352         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1353     }
1354     Reset(TRUE, FALSE);
1355     if (appData.noChessProgram || first.protocolVersion == 1) {
1356       InitBackEnd3();
1357     } else {
1358       /* kludge: allow timeout for initial "feature" commands */
1359       FreezeUI();
1360       DisplayMessage("", _("Starting chess program"));
1361       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1362     }
1363 }
1364
1365 int
1366 CalculateIndex (int index, int gameNr)
1367 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1368     int res;
1369     if(index > 0) return index; // fixed nmber
1370     if(index == 0) return 1;
1371     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1372     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1373     return res;
1374 }
1375
1376 int
1377 LoadGameOrPosition (int gameNr)
1378 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1379     if (*appData.loadGameFile != NULLCHAR) {
1380         if (!LoadGameFromFile(appData.loadGameFile,
1381                 CalculateIndex(appData.loadGameIndex, gameNr),
1382                               appData.loadGameFile, FALSE)) {
1383             DisplayFatalError(_("Bad game file"), 0, 1);
1384             return 0;
1385         }
1386     } else if (*appData.loadPositionFile != NULLCHAR) {
1387         if (!LoadPositionFromFile(appData.loadPositionFile,
1388                 CalculateIndex(appData.loadPositionIndex, gameNr),
1389                                   appData.loadPositionFile)) {
1390             DisplayFatalError(_("Bad position file"), 0, 1);
1391             return 0;
1392         }
1393     }
1394     return 1;
1395 }
1396
1397 void
1398 ReserveGame (int gameNr, char resChar)
1399 {
1400     FILE *tf = fopen(appData.tourneyFile, "r+");
1401     char *p, *q, c, buf[MSG_SIZ];
1402     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1403     safeStrCpy(buf, lastMsg, MSG_SIZ);
1404     DisplayMessage(_("Pick new game"), "");
1405     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1406     ParseArgsFromFile(tf);
1407     p = q = appData.results;
1408     if(appData.debugMode) {
1409       char *r = appData.participants;
1410       fprintf(debugFP, "results = '%s'\n", p);
1411       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1412       fprintf(debugFP, "\n");
1413     }
1414     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1415     nextGame = q - p;
1416     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1417     safeStrCpy(q, p, strlen(p) + 2);
1418     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1419     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1420     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1421         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1422         q[nextGame] = '*';
1423     }
1424     fseek(tf, -(strlen(p)+4), SEEK_END);
1425     c = fgetc(tf);
1426     if(c != '"') // depending on DOS or Unix line endings we can be one off
1427          fseek(tf, -(strlen(p)+2), SEEK_END);
1428     else fseek(tf, -(strlen(p)+3), SEEK_END);
1429     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1430     DisplayMessage(buf, "");
1431     free(p); appData.results = q;
1432     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1433        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1434       int round = appData.defaultMatchGames * appData.tourneyType;
1435       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1436          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1437         UnloadEngine(&first);  // next game belongs to other pairing;
1438         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1439     }
1440     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1441 }
1442
1443 void
1444 MatchEvent (int mode)
1445 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1446         int dummy;
1447         if(matchMode) { // already in match mode: switch it off
1448             abortMatch = TRUE;
1449             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1450             return;
1451         }
1452 //      if(gameMode != BeginningOfGame) {
1453 //          DisplayError(_("You can only start a match from the initial position."), 0);
1454 //          return;
1455 //      }
1456         abortMatch = FALSE;
1457         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1458         /* Set up machine vs. machine match */
1459         nextGame = 0;
1460         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1461         if(appData.tourneyFile[0]) {
1462             ReserveGame(-1, 0);
1463             if(nextGame > appData.matchGames) {
1464                 char buf[MSG_SIZ];
1465                 if(strchr(appData.results, '*') == NULL) {
1466                     FILE *f;
1467                     appData.tourneyCycles++;
1468                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1469                         fclose(f);
1470                         NextTourneyGame(-1, &dummy);
1471                         ReserveGame(-1, 0);
1472                         if(nextGame <= appData.matchGames) {
1473                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1474                             matchMode = mode;
1475                             ScheduleDelayedEvent(NextMatchGame, 10000);
1476                             return;
1477                         }
1478                     }
1479                 }
1480                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1481                 DisplayError(buf, 0);
1482                 appData.tourneyFile[0] = 0;
1483                 return;
1484             }
1485         } else
1486         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1487             DisplayFatalError(_("Can't have a match with no chess programs"),
1488                               0, 2);
1489             return;
1490         }
1491         matchMode = mode;
1492         matchGame = roundNr = 1;
1493         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1494         NextMatchGame();
1495 }
1496
1497 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1498
1499 void
1500 InitBackEnd3 P((void))
1501 {
1502     GameMode initialMode;
1503     char buf[MSG_SIZ];
1504     int err, len;
1505
1506     InitChessProgram(&first, startedFromSetupPosition);
1507
1508     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1509         free(programVersion);
1510         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1511         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1512         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1513     }
1514
1515     if (appData.icsActive) {
1516 #ifdef WIN32
1517         /* [DM] Make a console window if needed [HGM] merged ifs */
1518         ConsoleCreate();
1519 #endif
1520         err = establish();
1521         if (err != 0)
1522           {
1523             if (*appData.icsCommPort != NULLCHAR)
1524               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1525                              appData.icsCommPort);
1526             else
1527               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1528                         appData.icsHost, appData.icsPort);
1529
1530             if( (len >= MSG_SIZ) && appData.debugMode )
1531               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532
1533             DisplayFatalError(buf, err, 1);
1534             return;
1535         }
1536         SetICSMode();
1537         telnetISR =
1538           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1539         fromUserISR =
1540           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1541         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1542             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1543     } else if (appData.noChessProgram) {
1544         SetNCPMode();
1545     } else {
1546         SetGNUMode();
1547     }
1548
1549     if (*appData.cmailGameName != NULLCHAR) {
1550         SetCmailMode();
1551         OpenLoopback(&cmailPR);
1552         cmailISR =
1553           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1554     }
1555
1556     ThawUI();
1557     DisplayMessage("", "");
1558     if (StrCaseCmp(appData.initialMode, "") == 0) {
1559       initialMode = BeginningOfGame;
1560       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1561         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1562         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1563         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1564         ModeHighlight();
1565       }
1566     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1567       initialMode = TwoMachinesPlay;
1568     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1569       initialMode = AnalyzeFile;
1570     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1571       initialMode = AnalyzeMode;
1572     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1573       initialMode = MachinePlaysWhite;
1574     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1575       initialMode = MachinePlaysBlack;
1576     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1577       initialMode = EditGame;
1578     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1579       initialMode = EditPosition;
1580     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1581       initialMode = Training;
1582     } else {
1583       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1584       if( (len >= MSG_SIZ) && appData.debugMode )
1585         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586
1587       DisplayFatalError(buf, 0, 2);
1588       return;
1589     }
1590
1591     if (appData.matchMode) {
1592         if(appData.tourneyFile[0]) { // start tourney from command line
1593             FILE *f;
1594             if(f = fopen(appData.tourneyFile, "r")) {
1595                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1596                 fclose(f);
1597                 appData.clockMode = TRUE;
1598                 SetGNUMode();
1599             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1600         }
1601         MatchEvent(TRUE);
1602     } else if (*appData.cmailGameName != NULLCHAR) {
1603         /* Set up cmail mode */
1604         ReloadCmailMsgEvent(TRUE);
1605     } else {
1606         /* Set up other modes */
1607         if (initialMode == AnalyzeFile) {
1608           if (*appData.loadGameFile == NULLCHAR) {
1609             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1610             return;
1611           }
1612         }
1613         if (*appData.loadGameFile != NULLCHAR) {
1614             (void) LoadGameFromFile(appData.loadGameFile,
1615                                     appData.loadGameIndex,
1616                                     appData.loadGameFile, TRUE);
1617         } else if (*appData.loadPositionFile != NULLCHAR) {
1618             (void) LoadPositionFromFile(appData.loadPositionFile,
1619                                         appData.loadPositionIndex,
1620                                         appData.loadPositionFile);
1621             /* [HGM] try to make self-starting even after FEN load */
1622             /* to allow automatic setup of fairy variants with wtm */
1623             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1624                 gameMode = BeginningOfGame;
1625                 setboardSpoiledMachineBlack = 1;
1626             }
1627             /* [HGM] loadPos: make that every new game uses the setup */
1628             /* from file as long as we do not switch variant          */
1629             if(!blackPlaysFirst) {
1630                 startedFromPositionFile = TRUE;
1631                 CopyBoard(filePosition, boards[0]);
1632             }
1633         }
1634         if (initialMode == AnalyzeMode) {
1635           if (appData.noChessProgram) {
1636             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1637             return;
1638           }
1639           if (appData.icsActive) {
1640             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1641             return;
1642           }
1643           AnalyzeModeEvent();
1644         } else if (initialMode == AnalyzeFile) {
1645           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1646           ShowThinkingEvent();
1647           AnalyzeFileEvent();
1648           AnalysisPeriodicEvent(1);
1649         } else if (initialMode == MachinePlaysWhite) {
1650           if (appData.noChessProgram) {
1651             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1652                               0, 2);
1653             return;
1654           }
1655           if (appData.icsActive) {
1656             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1657                               0, 2);
1658             return;
1659           }
1660           MachineWhiteEvent();
1661         } else if (initialMode == MachinePlaysBlack) {
1662           if (appData.noChessProgram) {
1663             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1664                               0, 2);
1665             return;
1666           }
1667           if (appData.icsActive) {
1668             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1669                               0, 2);
1670             return;
1671           }
1672           MachineBlackEvent();
1673         } else if (initialMode == TwoMachinesPlay) {
1674           if (appData.noChessProgram) {
1675             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1676                               0, 2);
1677             return;
1678           }
1679           if (appData.icsActive) {
1680             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1681                               0, 2);
1682             return;
1683           }
1684           TwoMachinesEvent();
1685         } else if (initialMode == EditGame) {
1686           EditGameEvent();
1687         } else if (initialMode == EditPosition) {
1688           EditPositionEvent();
1689         } else if (initialMode == Training) {
1690           if (*appData.loadGameFile == NULLCHAR) {
1691             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1692             return;
1693           }
1694           TrainingEvent();
1695         }
1696     }
1697 }
1698
1699 void
1700 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1701 {
1702     DisplayBook(current+1);
1703
1704     MoveHistorySet( movelist, first, last, current, pvInfoList );
1705
1706     EvalGraphSet( first, last, current, pvInfoList );
1707
1708     MakeEngineOutputTitle();
1709 }
1710
1711 /*
1712  * Establish will establish a contact to a remote host.port.
1713  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1714  *  used to talk to the host.
1715  * Returns 0 if okay, error code if not.
1716  */
1717 int
1718 establish ()
1719 {
1720     char buf[MSG_SIZ];
1721
1722     if (*appData.icsCommPort != NULLCHAR) {
1723         /* Talk to the host through a serial comm port */
1724         return OpenCommPort(appData.icsCommPort, &icsPR);
1725
1726     } else if (*appData.gateway != NULLCHAR) {
1727         if (*appData.remoteShell == NULLCHAR) {
1728             /* Use the rcmd protocol to run telnet program on a gateway host */
1729             snprintf(buf, sizeof(buf), "%s %s %s",
1730                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1731             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1732
1733         } else {
1734             /* Use the rsh program to run telnet program on a gateway host */
1735             if (*appData.remoteUser == NULLCHAR) {
1736                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1737                         appData.gateway, appData.telnetProgram,
1738                         appData.icsHost, appData.icsPort);
1739             } else {
1740                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1741                         appData.remoteShell, appData.gateway,
1742                         appData.remoteUser, appData.telnetProgram,
1743                         appData.icsHost, appData.icsPort);
1744             }
1745             return StartChildProcess(buf, "", &icsPR);
1746
1747         }
1748     } else if (appData.useTelnet) {
1749         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1750
1751     } else {
1752         /* TCP socket interface differs somewhat between
1753            Unix and NT; handle details in the front end.
1754            */
1755         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1756     }
1757 }
1758
1759 void
1760 EscapeExpand (char *p, char *q)
1761 {       // [HGM] initstring: routine to shape up string arguments
1762         while(*p++ = *q++) if(p[-1] == '\\')
1763             switch(*q++) {
1764                 case 'n': p[-1] = '\n'; break;
1765                 case 'r': p[-1] = '\r'; break;
1766                 case 't': p[-1] = '\t'; break;
1767                 case '\\': p[-1] = '\\'; break;
1768                 case 0: *p = 0; return;
1769                 default: p[-1] = q[-1]; break;
1770             }
1771 }
1772
1773 void
1774 show_bytes (FILE *fp, char *buf, int count)
1775 {
1776     while (count--) {
1777         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1778             fprintf(fp, "\\%03o", *buf & 0xff);
1779         } else {
1780             putc(*buf, fp);
1781         }
1782         buf++;
1783     }
1784     fflush(fp);
1785 }
1786
1787 /* Returns an errno value */
1788 int
1789 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1790 {
1791     char buf[8192], *p, *q, *buflim;
1792     int left, newcount, outcount;
1793
1794     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1795         *appData.gateway != NULLCHAR) {
1796         if (appData.debugMode) {
1797             fprintf(debugFP, ">ICS: ");
1798             show_bytes(debugFP, message, count);
1799             fprintf(debugFP, "\n");
1800         }
1801         return OutputToProcess(pr, message, count, outError);
1802     }
1803
1804     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1805     p = message;
1806     q = buf;
1807     left = count;
1808     newcount = 0;
1809     while (left) {
1810         if (q >= buflim) {
1811             if (appData.debugMode) {
1812                 fprintf(debugFP, ">ICS: ");
1813                 show_bytes(debugFP, buf, newcount);
1814                 fprintf(debugFP, "\n");
1815             }
1816             outcount = OutputToProcess(pr, buf, newcount, outError);
1817             if (outcount < newcount) return -1; /* to be sure */
1818             q = buf;
1819             newcount = 0;
1820         }
1821         if (*p == '\n') {
1822             *q++ = '\r';
1823             newcount++;
1824         } else if (((unsigned char) *p) == TN_IAC) {
1825             *q++ = (char) TN_IAC;
1826             newcount ++;
1827         }
1828         *q++ = *p++;
1829         newcount++;
1830         left--;
1831     }
1832     if (appData.debugMode) {
1833         fprintf(debugFP, ">ICS: ");
1834         show_bytes(debugFP, buf, newcount);
1835         fprintf(debugFP, "\n");
1836     }
1837     outcount = OutputToProcess(pr, buf, newcount, outError);
1838     if (outcount < newcount) return -1; /* to be sure */
1839     return count;
1840 }
1841
1842 void
1843 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1844 {
1845     int outError, outCount;
1846     static int gotEof = 0;
1847
1848     /* Pass data read from player on to ICS */
1849     if (count > 0) {
1850         gotEof = 0;
1851         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1852         if (outCount < count) {
1853             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854         }
1855     } else if (count < 0) {
1856         RemoveInputSource(isr);
1857         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1858     } else if (gotEof++ > 0) {
1859         RemoveInputSource(isr);
1860         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1861     }
1862 }
1863
1864 void
1865 KeepAlive ()
1866 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1867     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1868     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1869     SendToICS("date\n");
1870     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1871 }
1872
1873 /* added routine for printf style output to ics */
1874 void
1875 ics_printf (char *format, ...)
1876 {
1877     char buffer[MSG_SIZ];
1878     va_list args;
1879
1880     va_start(args, format);
1881     vsnprintf(buffer, sizeof(buffer), format, args);
1882     buffer[sizeof(buffer)-1] = '\0';
1883     SendToICS(buffer);
1884     va_end(args);
1885 }
1886
1887 void
1888 SendToICS (char *s)
1889 {
1890     int count, outCount, outError;
1891
1892     if (icsPR == NoProc) return;
1893
1894     count = strlen(s);
1895     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1896     if (outCount < count) {
1897         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898     }
1899 }
1900
1901 /* This is used for sending logon scripts to the ICS. Sending
1902    without a delay causes problems when using timestamp on ICC
1903    (at least on my machine). */
1904 void
1905 SendToICSDelayed (char *s, long msdelay)
1906 {
1907     int count, outCount, outError;
1908
1909     if (icsPR == NoProc) return;
1910
1911     count = strlen(s);
1912     if (appData.debugMode) {
1913         fprintf(debugFP, ">ICS: ");
1914         show_bytes(debugFP, s, count);
1915         fprintf(debugFP, "\n");
1916     }
1917     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1918                                       msdelay);
1919     if (outCount < count) {
1920         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1921     }
1922 }
1923
1924
1925 /* Remove all highlighting escape sequences in s
1926    Also deletes any suffix starting with '('
1927    */
1928 char *
1929 StripHighlightAndTitle (char *s)
1930 {
1931     static char retbuf[MSG_SIZ];
1932     char *p = retbuf;
1933
1934     while (*s != NULLCHAR) {
1935         while (*s == '\033') {
1936             while (*s != NULLCHAR && !isalpha(*s)) s++;
1937             if (*s != NULLCHAR) s++;
1938         }
1939         while (*s != NULLCHAR && *s != '\033') {
1940             if (*s == '(' || *s == '[') {
1941                 *p = NULLCHAR;
1942                 return retbuf;
1943             }
1944             *p++ = *s++;
1945         }
1946     }
1947     *p = NULLCHAR;
1948     return retbuf;
1949 }
1950
1951 /* Remove all highlighting escape sequences in s */
1952 char *
1953 StripHighlight (char *s)
1954 {
1955     static char retbuf[MSG_SIZ];
1956     char *p = retbuf;
1957
1958     while (*s != NULLCHAR) {
1959         while (*s == '\033') {
1960             while (*s != NULLCHAR && !isalpha(*s)) s++;
1961             if (*s != NULLCHAR) s++;
1962         }
1963         while (*s != NULLCHAR && *s != '\033') {
1964             *p++ = *s++;
1965         }
1966     }
1967     *p = NULLCHAR;
1968     return retbuf;
1969 }
1970
1971 char *variantNames[] = VARIANT_NAMES;
1972 char *
1973 VariantName (VariantClass v)
1974 {
1975     return variantNames[v];
1976 }
1977
1978
1979 /* Identify a variant from the strings the chess servers use or the
1980    PGN Variant tag names we use. */
1981 VariantClass
1982 StringToVariant (char *e)
1983 {
1984     char *p;
1985     int wnum = -1;
1986     VariantClass v = VariantNormal;
1987     int i, found = FALSE;
1988     char buf[MSG_SIZ];
1989     int len;
1990
1991     if (!e) return v;
1992
1993     /* [HGM] skip over optional board-size prefixes */
1994     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1995         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1996         while( *e++ != '_');
1997     }
1998
1999     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2000         v = VariantNormal;
2001         found = TRUE;
2002     } else
2003     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2004       if (StrCaseStr(e, variantNames[i])) {
2005         v = (VariantClass) i;
2006         found = TRUE;
2007         break;
2008       }
2009     }
2010
2011     if (!found) {
2012       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2013           || StrCaseStr(e, "wild/fr")
2014           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2015         v = VariantFischeRandom;
2016       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2017                  (i = 1, p = StrCaseStr(e, "w"))) {
2018         p += i;
2019         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2020         if (isdigit(*p)) {
2021           wnum = atoi(p);
2022         } else {
2023           wnum = -1;
2024         }
2025         switch (wnum) {
2026         case 0: /* FICS only, actually */
2027         case 1:
2028           /* Castling legal even if K starts on d-file */
2029           v = VariantWildCastle;
2030           break;
2031         case 2:
2032         case 3:
2033         case 4:
2034           /* Castling illegal even if K & R happen to start in
2035              normal positions. */
2036           v = VariantNoCastle;
2037           break;
2038         case 5:
2039         case 7:
2040         case 8:
2041         case 10:
2042         case 11:
2043         case 12:
2044         case 13:
2045         case 14:
2046         case 15:
2047         case 18:
2048         case 19:
2049           /* Castling legal iff K & R start in normal positions */
2050           v = VariantNormal;
2051           break;
2052         case 6:
2053         case 20:
2054         case 21:
2055           /* Special wilds for position setup; unclear what to do here */
2056           v = VariantLoadable;
2057           break;
2058         case 9:
2059           /* Bizarre ICC game */
2060           v = VariantTwoKings;
2061           break;
2062         case 16:
2063           v = VariantKriegspiel;
2064           break;
2065         case 17:
2066           v = VariantLosers;
2067           break;
2068         case 22:
2069           v = VariantFischeRandom;
2070           break;
2071         case 23:
2072           v = VariantCrazyhouse;
2073           break;
2074         case 24:
2075           v = VariantBughouse;
2076           break;
2077         case 25:
2078           v = Variant3Check;
2079           break;
2080         case 26:
2081           /* Not quite the same as FICS suicide! */
2082           v = VariantGiveaway;
2083           break;
2084         case 27:
2085           v = VariantAtomic;
2086           break;
2087         case 28:
2088           v = VariantShatranj;
2089           break;
2090
2091         /* Temporary names for future ICC types.  The name *will* change in
2092            the next xboard/WinBoard release after ICC defines it. */
2093         case 29:
2094           v = Variant29;
2095           break;
2096         case 30:
2097           v = Variant30;
2098           break;
2099         case 31:
2100           v = Variant31;
2101           break;
2102         case 32:
2103           v = Variant32;
2104           break;
2105         case 33:
2106           v = Variant33;
2107           break;
2108         case 34:
2109           v = Variant34;
2110           break;
2111         case 35:
2112           v = Variant35;
2113           break;
2114         case 36:
2115           v = Variant36;
2116           break;
2117         case 37:
2118           v = VariantShogi;
2119           break;
2120         case 38:
2121           v = VariantXiangqi;
2122           break;
2123         case 39:
2124           v = VariantCourier;
2125           break;
2126         case 40:
2127           v = VariantGothic;
2128           break;
2129         case 41:
2130           v = VariantCapablanca;
2131           break;
2132         case 42:
2133           v = VariantKnightmate;
2134           break;
2135         case 43:
2136           v = VariantFairy;
2137           break;
2138         case 44:
2139           v = VariantCylinder;
2140           break;
2141         case 45:
2142           v = VariantFalcon;
2143           break;
2144         case 46:
2145           v = VariantCapaRandom;
2146           break;
2147         case 47:
2148           v = VariantBerolina;
2149           break;
2150         case 48:
2151           v = VariantJanus;
2152           break;
2153         case 49:
2154           v = VariantSuper;
2155           break;
2156         case 50:
2157           v = VariantGreat;
2158           break;
2159         case -1:
2160           /* Found "wild" or "w" in the string but no number;
2161              must assume it's normal chess. */
2162           v = VariantNormal;
2163           break;
2164         default:
2165           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2166           if( (len >= MSG_SIZ) && appData.debugMode )
2167             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2168
2169           DisplayError(buf, 0);
2170           v = VariantUnknown;
2171           break;
2172         }
2173       }
2174     }
2175     if (appData.debugMode) {
2176       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2177               e, wnum, VariantName(v));
2178     }
2179     return v;
2180 }
2181
2182 static int leftover_start = 0, leftover_len = 0;
2183 char star_match[STAR_MATCH_N][MSG_SIZ];
2184
2185 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2186    advance *index beyond it, and set leftover_start to the new value of
2187    *index; else return FALSE.  If pattern contains the character '*', it
2188    matches any sequence of characters not containing '\r', '\n', or the
2189    character following the '*' (if any), and the matched sequence(s) are
2190    copied into star_match.
2191    */
2192 int
2193 looking_at ( char *buf, int *index, char *pattern)
2194 {
2195     char *bufp = &buf[*index], *patternp = pattern;
2196     int star_count = 0;
2197     char *matchp = star_match[0];
2198
2199     for (;;) {
2200         if (*patternp == NULLCHAR) {
2201             *index = leftover_start = bufp - buf;
2202             *matchp = NULLCHAR;
2203             return TRUE;
2204         }
2205         if (*bufp == NULLCHAR) return FALSE;
2206         if (*patternp == '*') {
2207             if (*bufp == *(patternp + 1)) {
2208                 *matchp = NULLCHAR;
2209                 matchp = star_match[++star_count];
2210                 patternp += 2;
2211                 bufp++;
2212                 continue;
2213             } else if (*bufp == '\n' || *bufp == '\r') {
2214                 patternp++;
2215                 if (*patternp == NULLCHAR)
2216                   continue;
2217                 else
2218                   return FALSE;
2219             } else {
2220                 *matchp++ = *bufp++;
2221                 continue;
2222             }
2223         }
2224         if (*patternp != *bufp) return FALSE;
2225         patternp++;
2226         bufp++;
2227     }
2228 }
2229
2230 void
2231 SendToPlayer (char *data, int length)
2232 {
2233     int error, outCount;
2234     outCount = OutputToProcess(NoProc, data, length, &error);
2235     if (outCount < length) {
2236         DisplayFatalError(_("Error writing to display"), error, 1);
2237     }
2238 }
2239
2240 void
2241 PackHolding (char packed[], char *holding)
2242 {
2243     char *p = holding;
2244     char *q = packed;
2245     int runlength = 0;
2246     int curr = 9999;
2247     do {
2248         if (*p == curr) {
2249             runlength++;
2250         } else {
2251             switch (runlength) {
2252               case 0:
2253                 break;
2254               case 1:
2255                 *q++ = curr;
2256                 break;
2257               case 2:
2258                 *q++ = curr;
2259                 *q++ = curr;
2260                 break;
2261               default:
2262                 sprintf(q, "%d", runlength);
2263                 while (*q) q++;
2264                 *q++ = curr;
2265                 break;
2266             }
2267             runlength = 1;
2268             curr = *p;
2269         }
2270     } while (*p++);
2271     *q = NULLCHAR;
2272 }
2273
2274 /* Telnet protocol requests from the front end */
2275 void
2276 TelnetRequest (unsigned char ddww, unsigned char option)
2277 {
2278     unsigned char msg[3];
2279     int outCount, outError;
2280
2281     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2282
2283     if (appData.debugMode) {
2284         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2285         switch (ddww) {
2286           case TN_DO:
2287             ddwwStr = "DO";
2288             break;
2289           case TN_DONT:
2290             ddwwStr = "DONT";
2291             break;
2292           case TN_WILL:
2293             ddwwStr = "WILL";
2294             break;
2295           case TN_WONT:
2296             ddwwStr = "WONT";
2297             break;
2298           default:
2299             ddwwStr = buf1;
2300             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2301             break;
2302         }
2303         switch (option) {
2304           case TN_ECHO:
2305             optionStr = "ECHO";
2306             break;
2307           default:
2308             optionStr = buf2;
2309             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2310             break;
2311         }
2312         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2313     }
2314     msg[0] = TN_IAC;
2315     msg[1] = ddww;
2316     msg[2] = option;
2317     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2318     if (outCount < 3) {
2319         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2320     }
2321 }
2322
2323 void
2324 DoEcho ()
2325 {
2326     if (!appData.icsActive) return;
2327     TelnetRequest(TN_DO, TN_ECHO);
2328 }
2329
2330 void
2331 DontEcho ()
2332 {
2333     if (!appData.icsActive) return;
2334     TelnetRequest(TN_DONT, TN_ECHO);
2335 }
2336
2337 void
2338 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2339 {
2340     /* put the holdings sent to us by the server on the board holdings area */
2341     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2342     char p;
2343     ChessSquare piece;
2344
2345     if(gameInfo.holdingsWidth < 2)  return;
2346     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2347         return; // prevent overwriting by pre-board holdings
2348
2349     if( (int)lowestPiece >= BlackPawn ) {
2350         holdingsColumn = 0;
2351         countsColumn = 1;
2352         holdingsStartRow = BOARD_HEIGHT-1;
2353         direction = -1;
2354     } else {
2355         holdingsColumn = BOARD_WIDTH-1;
2356         countsColumn = BOARD_WIDTH-2;
2357         holdingsStartRow = 0;
2358         direction = 1;
2359     }
2360
2361     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2362         board[i][holdingsColumn] = EmptySquare;
2363         board[i][countsColumn]   = (ChessSquare) 0;
2364     }
2365     while( (p=*holdings++) != NULLCHAR ) {
2366         piece = CharToPiece( ToUpper(p) );
2367         if(piece == EmptySquare) continue;
2368         /*j = (int) piece - (int) WhitePawn;*/
2369         j = PieceToNumber(piece);
2370         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2371         if(j < 0) continue;               /* should not happen */
2372         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2373         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2374         board[holdingsStartRow+j*direction][countsColumn]++;
2375     }
2376 }
2377
2378
2379 void
2380 VariantSwitch (Board board, VariantClass newVariant)
2381 {
2382    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2383    static Board oldBoard;
2384
2385    startedFromPositionFile = FALSE;
2386    if(gameInfo.variant == newVariant) return;
2387
2388    /* [HGM] This routine is called each time an assignment is made to
2389     * gameInfo.variant during a game, to make sure the board sizes
2390     * are set to match the new variant. If that means adding or deleting
2391     * holdings, we shift the playing board accordingly
2392     * This kludge is needed because in ICS observe mode, we get boards
2393     * of an ongoing game without knowing the variant, and learn about the
2394     * latter only later. This can be because of the move list we requested,
2395     * in which case the game history is refilled from the beginning anyway,
2396     * but also when receiving holdings of a crazyhouse game. In the latter
2397     * case we want to add those holdings to the already received position.
2398     */
2399
2400
2401    if (appData.debugMode) {
2402      fprintf(debugFP, "Switch board from %s to %s\n",
2403              VariantName(gameInfo.variant), VariantName(newVariant));
2404      setbuf(debugFP, NULL);
2405    }
2406    shuffleOpenings = 0;       /* [HGM] shuffle */
2407    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2408    switch(newVariant)
2409      {
2410      case VariantShogi:
2411        newWidth = 9;  newHeight = 9;
2412        gameInfo.holdingsSize = 7;
2413      case VariantBughouse:
2414      case VariantCrazyhouse:
2415        newHoldingsWidth = 2; break;
2416      case VariantGreat:
2417        newWidth = 10;
2418      case VariantSuper:
2419        newHoldingsWidth = 2;
2420        gameInfo.holdingsSize = 8;
2421        break;
2422      case VariantGothic:
2423      case VariantCapablanca:
2424      case VariantCapaRandom:
2425        newWidth = 10;
2426      default:
2427        newHoldingsWidth = gameInfo.holdingsSize = 0;
2428      };
2429
2430    if(newWidth  != gameInfo.boardWidth  ||
2431       newHeight != gameInfo.boardHeight ||
2432       newHoldingsWidth != gameInfo.holdingsWidth ) {
2433
2434      /* shift position to new playing area, if needed */
2435      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440        for(i=0; i<newHeight; i++) {
2441          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2442          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2443        }
2444      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2445        for(i=0; i<BOARD_HEIGHT; i++)
2446          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2447            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2448              board[i][j];
2449      }
2450      board[HOLDINGS_SET] = 0;
2451      gameInfo.boardWidth  = newWidth;
2452      gameInfo.boardHeight = newHeight;
2453      gameInfo.holdingsWidth = newHoldingsWidth;
2454      gameInfo.variant = newVariant;
2455      InitDrawingSizes(-2, 0);
2456    } else gameInfo.variant = newVariant;
2457    CopyBoard(oldBoard, board);   // remember correctly formatted board
2458      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2459    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2460 }
2461
2462 static int loggedOn = FALSE;
2463
2464 /*-- Game start info cache: --*/
2465 int gs_gamenum;
2466 char gs_kind[MSG_SIZ];
2467 static char player1Name[128] = "";
2468 static char player2Name[128] = "";
2469 static char cont_seq[] = "\n\\   ";
2470 static int player1Rating = -1;
2471 static int player2Rating = -1;
2472 /*----------------------------*/
2473
2474 ColorClass curColor = ColorNormal;
2475 int suppressKibitz = 0;
2476
2477 // [HGM] seekgraph
2478 Boolean soughtPending = FALSE;
2479 Boolean seekGraphUp;
2480 #define MAX_SEEK_ADS 200
2481 #define SQUARE 0x80
2482 char *seekAdList[MAX_SEEK_ADS];
2483 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2484 float tcList[MAX_SEEK_ADS];
2485 char colorList[MAX_SEEK_ADS];
2486 int nrOfSeekAds = 0;
2487 int minRating = 1010, maxRating = 2800;
2488 int hMargin = 10, vMargin = 20, h, w;
2489 extern int squareSize, lineGap;
2490
2491 void
2492 PlotSeekAd (int i)
2493 {
2494         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2495         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2496         if(r < minRating+100 && r >=0 ) r = minRating+100;
2497         if(r > maxRating) r = maxRating;
2498         if(tc < 1.f) tc = 1.f;
2499         if(tc > 95.f) tc = 95.f;
2500         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2501         y = ((double)r - minRating)/(maxRating - minRating)
2502             * (h-vMargin-squareSize/8-1) + vMargin;
2503         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2504         if(strstr(seekAdList[i], " u ")) color = 1;
2505         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2506            !strstr(seekAdList[i], "bullet") &&
2507            !strstr(seekAdList[i], "blitz") &&
2508            !strstr(seekAdList[i], "standard") ) color = 2;
2509         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2510         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2511 }
2512
2513 void
2514 PlotSingleSeekAd (int i)
2515 {
2516         PlotSeekAd(i);
2517 }
2518
2519 void
2520 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2521 {
2522         char buf[MSG_SIZ], *ext = "";
2523         VariantClass v = StringToVariant(type);
2524         if(strstr(type, "wild")) {
2525             ext = type + 4; // append wild number
2526             if(v == VariantFischeRandom) type = "chess960"; else
2527             if(v == VariantLoadable) type = "setup"; else
2528             type = VariantName(v);
2529         }
2530         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2531         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2532             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2533             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2534             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2535             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2536             seekNrList[nrOfSeekAds] = nr;
2537             zList[nrOfSeekAds] = 0;
2538             seekAdList[nrOfSeekAds++] = StrSave(buf);
2539             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2540         }
2541 }
2542
2543 void
2544 EraseSeekDot (int i)
2545 {
2546     int x = xList[i], y = yList[i], d=squareSize/4, k;
2547     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2548     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2549     // now replot every dot that overlapped
2550     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2551         int xx = xList[k], yy = yList[k];
2552         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2553             DrawSeekDot(xx, yy, colorList[k]);
2554     }
2555 }
2556
2557 void
2558 RemoveSeekAd (int nr)
2559 {
2560         int i;
2561         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2562             EraseSeekDot(i);
2563             if(seekAdList[i]) free(seekAdList[i]);
2564             seekAdList[i] = seekAdList[--nrOfSeekAds];
2565             seekNrList[i] = seekNrList[nrOfSeekAds];
2566             ratingList[i] = ratingList[nrOfSeekAds];
2567             colorList[i]  = colorList[nrOfSeekAds];
2568             tcList[i] = tcList[nrOfSeekAds];
2569             xList[i]  = xList[nrOfSeekAds];
2570             yList[i]  = yList[nrOfSeekAds];
2571             zList[i]  = zList[nrOfSeekAds];
2572             seekAdList[nrOfSeekAds] = NULL;
2573             break;
2574         }
2575 }
2576
2577 Boolean
2578 MatchSoughtLine (char *line)
2579 {
2580     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2581     int nr, base, inc, u=0; char dummy;
2582
2583     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2584        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2585        (u=1) &&
2586        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2587         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2588         // match: compact and save the line
2589         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2590         return TRUE;
2591     }
2592     return FALSE;
2593 }
2594
2595 int
2596 DrawSeekGraph ()
2597 {
2598     int i;
2599     if(!seekGraphUp) return FALSE;
2600     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2601     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2602
2603     DrawSeekBackground(0, 0, w, h);
2604     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2605     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2606     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2607         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2608         yy = h-1-yy;
2609         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2610         if(i%500 == 0) {
2611             char buf[MSG_SIZ];
2612             snprintf(buf, MSG_SIZ, "%d", i);
2613             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2614         }
2615     }
2616     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2617     for(i=1; i<100; i+=(i<10?1:5)) {
2618         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2619         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2620         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2621             char buf[MSG_SIZ];
2622             snprintf(buf, MSG_SIZ, "%d", i);
2623             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2624         }
2625     }
2626     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2627     return TRUE;
2628 }
2629
2630 int
2631 SeekGraphClick (ClickType click, int x, int y, int moving)
2632 {
2633     static int lastDown = 0, displayed = 0, lastSecond;
2634     if(y < 0) return FALSE;
2635     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2636         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2637         if(!seekGraphUp) return FALSE;
2638         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2639         DrawPosition(TRUE, NULL);
2640         return TRUE;
2641     }
2642     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2643         if(click == Release || moving) return FALSE;
2644         nrOfSeekAds = 0;
2645         soughtPending = TRUE;
2646         SendToICS(ics_prefix);
2647         SendToICS("sought\n"); // should this be "sought all"?
2648     } else { // issue challenge based on clicked ad
2649         int dist = 10000; int i, closest = 0, second = 0;
2650         for(i=0; i<nrOfSeekAds; i++) {
2651             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2652             if(d < dist) { dist = d; closest = i; }
2653             second += (d - zList[i] < 120); // count in-range ads
2654             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2655         }
2656         if(dist < 120) {
2657             char buf[MSG_SIZ];
2658             second = (second > 1);
2659             if(displayed != closest || second != lastSecond) {
2660                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2661                 lastSecond = second; displayed = closest;
2662             }
2663             if(click == Press) {
2664                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2665                 lastDown = closest;
2666                 return TRUE;
2667             } // on press 'hit', only show info
2668             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2669             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2670             SendToICS(ics_prefix);
2671             SendToICS(buf);
2672             return TRUE; // let incoming board of started game pop down the graph
2673         } else if(click == Release) { // release 'miss' is ignored
2674             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2675             if(moving == 2) { // right up-click
2676                 nrOfSeekAds = 0; // refresh graph
2677                 soughtPending = TRUE;
2678                 SendToICS(ics_prefix);
2679                 SendToICS("sought\n"); // should this be "sought all"?
2680             }
2681             return TRUE;
2682         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2683         // press miss or release hit 'pop down' seek graph
2684         seekGraphUp = FALSE;
2685         DrawPosition(TRUE, NULL);
2686     }
2687     return TRUE;
2688 }
2689
2690 void
2691 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2692 {
2693 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2694 #define STARTED_NONE 0
2695 #define STARTED_MOVES 1
2696 #define STARTED_BOARD 2
2697 #define STARTED_OBSERVE 3
2698 #define STARTED_HOLDINGS 4
2699 #define STARTED_CHATTER 5
2700 #define STARTED_COMMENT 6
2701 #define STARTED_MOVES_NOHIDE 7
2702
2703     static int started = STARTED_NONE;
2704     static char parse[20000];
2705     static int parse_pos = 0;
2706     static char buf[BUF_SIZE + 1];
2707     static int firstTime = TRUE, intfSet = FALSE;
2708     static ColorClass prevColor = ColorNormal;
2709     static int savingComment = FALSE;
2710     static int cmatch = 0; // continuation sequence match
2711     char *bp;
2712     char str[MSG_SIZ];
2713     int i, oldi;
2714     int buf_len;
2715     int next_out;
2716     int tkind;
2717     int backup;    /* [DM] For zippy color lines */
2718     char *p;
2719     char talker[MSG_SIZ]; // [HGM] chat
2720     int channel;
2721
2722     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2723
2724     if (appData.debugMode) {
2725       if (!error) {
2726         fprintf(debugFP, "<ICS: ");
2727         show_bytes(debugFP, data, count);
2728         fprintf(debugFP, "\n");
2729       }
2730     }
2731
2732     if (appData.debugMode) { int f = forwardMostMove;
2733         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2734                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2735                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2736     }
2737     if (count > 0) {
2738         /* If last read ended with a partial line that we couldn't parse,
2739            prepend it to the new read and try again. */
2740         if (leftover_len > 0) {
2741             for (i=0; i<leftover_len; i++)
2742               buf[i] = buf[leftover_start + i];
2743         }
2744
2745     /* copy new characters into the buffer */
2746     bp = buf + leftover_len;
2747     buf_len=leftover_len;
2748     for (i=0; i<count; i++)
2749     {
2750         // ignore these
2751         if (data[i] == '\r')
2752             continue;
2753
2754         // join lines split by ICS?
2755         if (!appData.noJoin)
2756         {
2757             /*
2758                 Joining just consists of finding matches against the
2759                 continuation sequence, and discarding that sequence
2760                 if found instead of copying it.  So, until a match
2761                 fails, there's nothing to do since it might be the
2762                 complete sequence, and thus, something we don't want
2763                 copied.
2764             */
2765             if (data[i] == cont_seq[cmatch])
2766             {
2767                 cmatch++;
2768                 if (cmatch == strlen(cont_seq))
2769                 {
2770                     cmatch = 0; // complete match.  just reset the counter
2771
2772                     /*
2773                         it's possible for the ICS to not include the space
2774                         at the end of the last word, making our [correct]
2775                         join operation fuse two separate words.  the server
2776                         does this when the space occurs at the width setting.
2777                     */
2778                     if (!buf_len || buf[buf_len-1] != ' ')
2779                     {
2780                         *bp++ = ' ';
2781                         buf_len++;
2782                     }
2783                 }
2784                 continue;
2785             }
2786             else if (cmatch)
2787             {
2788                 /*
2789                     match failed, so we have to copy what matched before
2790                     falling through and copying this character.  In reality,
2791                     this will only ever be just the newline character, but
2792                     it doesn't hurt to be precise.
2793                 */
2794                 strncpy(bp, cont_seq, cmatch);
2795                 bp += cmatch;
2796                 buf_len += cmatch;
2797                 cmatch = 0;
2798             }
2799         }
2800
2801         // copy this char
2802         *bp++ = data[i];
2803         buf_len++;
2804     }
2805
2806         buf[buf_len] = NULLCHAR;
2807 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2808         next_out = 0;
2809         leftover_start = 0;
2810
2811         i = 0;
2812         while (i < buf_len) {
2813             /* Deal with part of the TELNET option negotiation
2814                protocol.  We refuse to do anything beyond the
2815                defaults, except that we allow the WILL ECHO option,
2816                which ICS uses to turn off password echoing when we are
2817                directly connected to it.  We reject this option
2818                if localLineEditing mode is on (always on in xboard)
2819                and we are talking to port 23, which might be a real
2820                telnet server that will try to keep WILL ECHO on permanently.
2821              */
2822             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2823                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2824                 unsigned char option;
2825                 oldi = i;
2826                 switch ((unsigned char) buf[++i]) {
2827                   case TN_WILL:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<WILL ");
2830                     switch (option = (unsigned char) buf[++i]) {
2831                       case TN_ECHO:
2832                         if (appData.debugMode)
2833                           fprintf(debugFP, "ECHO ");
2834                         /* Reply only if this is a change, according
2835                            to the protocol rules. */
2836                         if (remoteEchoOption) break;
2837                         if (appData.localLineEditing &&
2838                             atoi(appData.icsPort) == TN_PORT) {
2839                             TelnetRequest(TN_DONT, TN_ECHO);
2840                         } else {
2841                             EchoOff();
2842                             TelnetRequest(TN_DO, TN_ECHO);
2843                             remoteEchoOption = TRUE;
2844                         }
2845                         break;
2846                       default:
2847                         if (appData.debugMode)
2848                           fprintf(debugFP, "%d ", option);
2849                         /* Whatever this is, we don't want it. */
2850                         TelnetRequest(TN_DONT, option);
2851                         break;
2852                     }
2853                     break;
2854                   case TN_WONT:
2855                     if (appData.debugMode)
2856                       fprintf(debugFP, "\n<WONT ");
2857                     switch (option = (unsigned char) buf[++i]) {
2858                       case TN_ECHO:
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "ECHO ");
2861                         /* Reply only if this is a change, according
2862                            to the protocol rules. */
2863                         if (!remoteEchoOption) break;
2864                         EchoOn();
2865                         TelnetRequest(TN_DONT, TN_ECHO);
2866                         remoteEchoOption = FALSE;
2867                         break;
2868                       default:
2869                         if (appData.debugMode)
2870                           fprintf(debugFP, "%d ", (unsigned char) option);
2871                         /* Whatever this is, it must already be turned
2872                            off, because we never agree to turn on
2873                            anything non-default, so according to the
2874                            protocol rules, we don't reply. */
2875                         break;
2876                     }
2877                     break;
2878                   case TN_DO:
2879                     if (appData.debugMode)
2880                       fprintf(debugFP, "\n<DO ");
2881                     switch (option = (unsigned char) buf[++i]) {
2882                       default:
2883                         /* Whatever this is, we refuse to do it. */
2884                         if (appData.debugMode)
2885                           fprintf(debugFP, "%d ", option);
2886                         TelnetRequest(TN_WONT, option);
2887                         break;
2888                     }
2889                     break;
2890                   case TN_DONT:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<DONT ");
2893                     switch (option = (unsigned char) buf[++i]) {
2894                       default:
2895                         if (appData.debugMode)
2896                           fprintf(debugFP, "%d ", option);
2897                         /* Whatever this is, we are already not doing
2898                            it, because we never agree to do anything
2899                            non-default, so according to the protocol
2900                            rules, we don't reply. */
2901                         break;
2902                     }
2903                     break;
2904                   case TN_IAC:
2905                     if (appData.debugMode)
2906                       fprintf(debugFP, "\n<IAC ");
2907                     /* Doubled IAC; pass it through */
2908                     i--;
2909                     break;
2910                   default:
2911                     if (appData.debugMode)
2912                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2913                     /* Drop all other telnet commands on the floor */
2914                     break;
2915                 }
2916                 if (oldi > next_out)
2917                   SendToPlayer(&buf[next_out], oldi - next_out);
2918                 if (++i > next_out)
2919                   next_out = i;
2920                 continue;
2921             }
2922
2923             /* OK, this at least will *usually* work */
2924             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2925                 loggedOn = TRUE;
2926             }
2927
2928             if (loggedOn && !intfSet) {
2929                 if (ics_type == ICS_ICC) {
2930                   snprintf(str, MSG_SIZ,
2931                           "/set-quietly interface %s\n/set-quietly style 12\n",
2932                           programVersion);
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2935                 } else if (ics_type == ICS_CHESSNET) {
2936                   snprintf(str, MSG_SIZ, "/style 12\n");
2937                 } else {
2938                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2939                   strcat(str, programVersion);
2940                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2941                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2942                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2943 #ifdef WIN32
2944                   strcat(str, "$iset nohighlight 1\n");
2945 #endif
2946                   strcat(str, "$iset lock 1\n$style 12\n");
2947                 }
2948                 SendToICS(str);
2949                 NotifyFrontendLogin();
2950                 intfSet = TRUE;
2951             }
2952
2953             if (started == STARTED_COMMENT) {
2954                 /* Accumulate characters in comment */
2955                 parse[parse_pos++] = buf[i];
2956                 if (buf[i] == '\n') {
2957                     parse[parse_pos] = NULLCHAR;
2958                     if(chattingPartner>=0) {
2959                         char mess[MSG_SIZ];
2960                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2961                         OutputChatMessage(chattingPartner, mess);
2962                         chattingPartner = -1;
2963                         next_out = i+1; // [HGM] suppress printing in ICS window
2964                     } else
2965                     if(!suppressKibitz) // [HGM] kibitz
2966                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2967                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2968                         int nrDigit = 0, nrAlph = 0, j;
2969                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2970                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2971                         parse[parse_pos] = NULLCHAR;
2972                         // try to be smart: if it does not look like search info, it should go to
2973                         // ICS interaction window after all, not to engine-output window.
2974                         for(j=0; j<parse_pos; j++) { // count letters and digits
2975                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2976                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2977                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2978                         }
2979                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2980                             int depth=0; float score;
2981                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2982                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2983                                 pvInfoList[forwardMostMove-1].depth = depth;
2984                                 pvInfoList[forwardMostMove-1].score = 100*score;
2985                             }
2986                             OutputKibitz(suppressKibitz, parse);
2987                         } else {
2988                             char tmp[MSG_SIZ];
2989                             if(gameMode == IcsObserving) // restore original ICS messages
2990                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2991                             else
2992                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2993                             SendToPlayer(tmp, strlen(tmp));
2994                         }
2995                         next_out = i+1; // [HGM] suppress printing in ICS window
2996                     }
2997                     started = STARTED_NONE;
2998                 } else {
2999                     /* Don't match patterns against characters in comment */
3000                     i++;
3001                     continue;
3002                 }
3003             }
3004             if (started == STARTED_CHATTER) {
3005                 if (buf[i] != '\n') {
3006                     /* Don't match patterns against characters in chatter */
3007                     i++;
3008                     continue;
3009                 }
3010                 started = STARTED_NONE;
3011                 if(suppressKibitz) next_out = i+1;
3012             }
3013
3014             /* Kludge to deal with rcmd protocol */
3015             if (firstTime && looking_at(buf, &i, "\001*")) {
3016                 DisplayFatalError(&buf[1], 0, 1);
3017                 continue;
3018             } else {
3019                 firstTime = FALSE;
3020             }
3021
3022             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3023                 ics_type = ICS_ICC;
3024                 ics_prefix = "/";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3030                 ics_type = ICS_FICS;
3031                 ics_prefix = "$";
3032                 if (appData.debugMode)
3033                   fprintf(debugFP, "ics_type %d\n", ics_type);
3034                 continue;
3035             }
3036             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3037                 ics_type = ICS_CHESSNET;
3038                 ics_prefix = "/";
3039                 if (appData.debugMode)
3040                   fprintf(debugFP, "ics_type %d\n", ics_type);
3041                 continue;
3042             }
3043
3044             if (!loggedOn &&
3045                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3046                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3047                  looking_at(buf, &i, "will be \"*\""))) {
3048               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3049               continue;
3050             }
3051
3052             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3053               char buf[MSG_SIZ];
3054               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3055               DisplayIcsInteractionTitle(buf);
3056               have_set_title = TRUE;
3057             }
3058
3059             /* skip finger notes */
3060             if (started == STARTED_NONE &&
3061                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3062                  (buf[i] == '1' && buf[i+1] == '0')) &&
3063                 buf[i+2] == ':' && buf[i+3] == ' ') {
3064               started = STARTED_CHATTER;
3065               i += 3;
3066               continue;
3067             }
3068
3069             oldi = i;
3070             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3071             if(appData.seekGraph) {
3072                 if(soughtPending && MatchSoughtLine(buf+i)) {
3073                     i = strstr(buf+i, "rated") - buf;
3074                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075                     next_out = leftover_start = i;
3076                     started = STARTED_CHATTER;
3077                     suppressKibitz = TRUE;
3078                     continue;
3079                 }
3080                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3081                         && looking_at(buf, &i, "* ads displayed")) {
3082                     soughtPending = FALSE;
3083                     seekGraphUp = TRUE;
3084                     DrawSeekGraph();
3085                     continue;
3086                 }
3087                 if(appData.autoRefresh) {
3088                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3089                         int s = (ics_type == ICS_ICC); // ICC format differs
3090                         if(seekGraphUp)
3091                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3092                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3093                         looking_at(buf, &i, "*% "); // eat prompt
3094                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3095                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                         next_out = i; // suppress
3097                         continue;
3098                     }
3099                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3100                         char *p = star_match[0];
3101                         while(*p) {
3102                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3103                             while(*p && *p++ != ' '); // next
3104                         }
3105                         looking_at(buf, &i, "*% "); // eat prompt
3106                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                         next_out = i;
3108                         continue;
3109                     }
3110                 }
3111             }
3112
3113             /* skip formula vars */
3114             if (started == STARTED_NONE &&
3115                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3116               started = STARTED_CHATTER;
3117               i += 3;
3118               continue;
3119             }
3120
3121             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3122             if (appData.autoKibitz && started == STARTED_NONE &&
3123                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3124                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3125                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3126                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3127                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3128                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3129                         suppressKibitz = TRUE;
3130                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3131                         next_out = i;
3132                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3133                                 && (gameMode == IcsPlayingWhite)) ||
3134                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3135                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3136                             started = STARTED_CHATTER; // own kibitz we simply discard
3137                         else {
3138                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3139                             parse_pos = 0; parse[0] = NULLCHAR;
3140                             savingComment = TRUE;
3141                             suppressKibitz = gameMode != IcsObserving ? 2 :
3142                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3143                         }
3144                         continue;
3145                 } else
3146                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3147                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3148                          && atoi(star_match[0])) {
3149                     // suppress the acknowledgements of our own autoKibitz
3150                     char *p;
3151                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3153                     SendToPlayer(star_match[0], strlen(star_match[0]));
3154                     if(looking_at(buf, &i, "*% ")) // eat prompt
3155                         suppressKibitz = FALSE;
3156                     next_out = i;
3157                     continue;
3158                 }
3159             } // [HGM] kibitz: end of patch
3160
3161             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3162
3163             // [HGM] chat: intercept tells by users for which we have an open chat window
3164             channel = -1;
3165             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3166                                            looking_at(buf, &i, "* whispers:") ||
3167                                            looking_at(buf, &i, "* kibitzes:") ||
3168                                            looking_at(buf, &i, "* shouts:") ||
3169                                            looking_at(buf, &i, "* c-shouts:") ||
3170                                            looking_at(buf, &i, "--> * ") ||
3171                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3172                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3173                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3174                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3175                 int p;
3176                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3177                 chattingPartner = -1;
3178
3179                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3182                     talker[0] = '['; strcat(talker, "] ");
3183                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3184                     chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3188                 for(p=0; p<MAX_CHAT; p++) {
3189                     if(!strcmp("kibitzes", chatPartner[p])) {
3190                         talker[0] = '['; strcat(talker, "] ");
3191                         chattingPartner = p; break;
3192                     }
3193                 } else
3194                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3195                 for(p=0; p<MAX_CHAT; p++) {
3196                     if(!strcmp("whispers", chatPartner[p])) {
3197                         talker[0] = '['; strcat(talker, "] ");
3198                         chattingPartner = p; break;
3199                     }
3200                 } else
3201                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3202                   if(buf[i-8] == '-' && buf[i-3] == 't')
3203                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3204                     if(!strcmp("c-shouts", chatPartner[p])) {
3205                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3206                         chattingPartner = p; break;
3207                     }
3208                   }
3209                   if(chattingPartner < 0)
3210                   for(p=0; p<MAX_CHAT; p++) {
3211                     if(!strcmp("shouts", chatPartner[p])) {
3212                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3213                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3214                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3215                         chattingPartner = p; break;
3216                     }
3217                   }
3218                 }
3219                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3220                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3221                     talker[0] = 0; Colorize(ColorTell, FALSE);
3222                     chattingPartner = p; break;
3223                 }
3224                 if(chattingPartner<0) i = oldi; else {
3225                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3226                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3227                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228                     started = STARTED_COMMENT;
3229                     parse_pos = 0; parse[0] = NULLCHAR;
3230                     savingComment = 3 + chattingPartner; // counts as TRUE
3231                     suppressKibitz = TRUE;
3232                     continue;
3233                 }
3234             } // [HGM] chat: end of patch
3235
3236           backup = i;
3237             if (appData.zippyTalk || appData.zippyPlay) {
3238                 /* [DM] Backup address for color zippy lines */
3239 #if ZIPPY
3240                if (loggedOn == TRUE)
3241                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3242                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3243 #endif
3244             } // [DM] 'else { ' deleted
3245                 if (
3246                     /* Regular tells and says */
3247                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3248                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3249                     looking_at(buf, &i, "* says: ") ||
3250                     /* Don't color "message" or "messages" output */
3251                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3252                     looking_at(buf, &i, "*. * at *:*: ") ||
3253                     looking_at(buf, &i, "--* (*:*): ") ||
3254                     /* Message notifications (same color as tells) */
3255                     looking_at(buf, &i, "* has left a message ") ||
3256                     looking_at(buf, &i, "* just sent you a message:\n") ||
3257                     /* Whispers and kibitzes */
3258                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3259                     looking_at(buf, &i, "* kibitzes: ") ||
3260                     /* Channel tells */
3261                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3262
3263                   if (tkind == 1 && strchr(star_match[0], ':')) {
3264                       /* Avoid "tells you:" spoofs in channels */
3265                      tkind = 3;
3266                   }
3267                   if (star_match[0][0] == NULLCHAR ||
3268                       strchr(star_match[0], ' ') ||
3269                       (tkind == 3 && strchr(star_match[1], ' '))) {
3270                     /* Reject bogus matches */
3271                     i = oldi;
3272                   } else {
3273                     if (appData.colorize) {
3274                       if (oldi > next_out) {
3275                         SendToPlayer(&buf[next_out], oldi - next_out);
3276                         next_out = oldi;
3277                       }
3278                       switch (tkind) {
3279                       case 1:
3280                         Colorize(ColorTell, FALSE);
3281                         curColor = ColorTell;
3282                         break;
3283                       case 2:
3284                         Colorize(ColorKibitz, FALSE);
3285                         curColor = ColorKibitz;
3286                         break;
3287                       case 3:
3288                         p = strrchr(star_match[1], '(');
3289                         if (p == NULL) {
3290                           p = star_match[1];
3291                         } else {
3292                           p++;
3293                         }
3294                         if (atoi(p) == 1) {
3295                           Colorize(ColorChannel1, FALSE);
3296                           curColor = ColorChannel1;
3297                         } else {
3298                           Colorize(ColorChannel, FALSE);
3299                           curColor = ColorChannel;
3300                         }
3301                         break;
3302                       case 5:
3303                         curColor = ColorNormal;
3304                         break;
3305                       }
3306                     }
3307                     if (started == STARTED_NONE && appData.autoComment &&
3308                         (gameMode == IcsObserving ||
3309                          gameMode == IcsPlayingWhite ||
3310                          gameMode == IcsPlayingBlack)) {
3311                       parse_pos = i - oldi;
3312                       memcpy(parse, &buf[oldi], parse_pos);
3313                       parse[parse_pos] = NULLCHAR;
3314                       started = STARTED_COMMENT;
3315                       savingComment = TRUE;
3316                     } else {
3317                       started = STARTED_CHATTER;
3318                       savingComment = FALSE;
3319                     }
3320                     loggedOn = TRUE;
3321                     continue;
3322                   }
3323                 }
3324
3325                 if (looking_at(buf, &i, "* s-shouts: ") ||
3326                     looking_at(buf, &i, "* c-shouts: ")) {
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorSShout, FALSE);
3333                         curColor = ColorSShout;
3334                     }
3335                     loggedOn = TRUE;
3336                     started = STARTED_CHATTER;
3337                     continue;
3338                 }
3339
3340                 if (looking_at(buf, &i, "--->")) {
3341                     loggedOn = TRUE;
3342                     continue;
3343                 }
3344
3345                 if (looking_at(buf, &i, "* shouts: ") ||
3346                     looking_at(buf, &i, "--> ")) {
3347                     if (appData.colorize) {
3348                         if (oldi > next_out) {
3349                             SendToPlayer(&buf[next_out], oldi - next_out);
3350                             next_out = oldi;
3351                         }
3352                         Colorize(ColorShout, FALSE);
3353                         curColor = ColorShout;
3354                     }
3355                     loggedOn = TRUE;
3356                     started = STARTED_CHATTER;
3357                     continue;
3358                 }
3359
3360                 if (looking_at( buf, &i, "Challenge:")) {
3361                     if (appData.colorize) {
3362                         if (oldi > next_out) {
3363                             SendToPlayer(&buf[next_out], oldi - next_out);
3364                             next_out = oldi;
3365                         }
3366                         Colorize(ColorChallenge, FALSE);
3367                         curColor = ColorChallenge;
3368                     }
3369                     loggedOn = TRUE;
3370                     continue;
3371                 }
3372
3373                 if (looking_at(buf, &i, "* offers you") ||
3374                     looking_at(buf, &i, "* offers to be") ||
3375                     looking_at(buf, &i, "* would like to") ||
3376                     looking_at(buf, &i, "* requests to") ||
3377                     looking_at(buf, &i, "Your opponent offers") ||
3378                     looking_at(buf, &i, "Your opponent requests")) {
3379
3380                     if (appData.colorize) {
3381                         if (oldi > next_out) {
3382                             SendToPlayer(&buf[next_out], oldi - next_out);
3383                             next_out = oldi;
3384                         }
3385                         Colorize(ColorRequest, FALSE);
3386                         curColor = ColorRequest;
3387                     }
3388                     continue;
3389                 }
3390
3391                 if (looking_at(buf, &i, "* (*) seeking")) {
3392                     if (appData.colorize) {
3393                         if (oldi > next_out) {
3394                             SendToPlayer(&buf[next_out], oldi - next_out);
3395                             next_out = oldi;
3396                         }
3397                         Colorize(ColorSeek, FALSE);
3398                         curColor = ColorSeek;
3399                     }
3400                     continue;
3401             }
3402
3403           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3404
3405             if (looking_at(buf, &i, "\\   ")) {
3406                 if (prevColor != ColorNormal) {
3407                     if (oldi > next_out) {
3408                         SendToPlayer(&buf[next_out], oldi - next_out);
3409                         next_out = oldi;
3410                     }
3411                     Colorize(prevColor, TRUE);
3412                     curColor = prevColor;
3413                 }
3414                 if (savingComment) {
3415                     parse_pos = i - oldi;
3416                     memcpy(parse, &buf[oldi], parse_pos);
3417                     parse[parse_pos] = NULLCHAR;
3418                     started = STARTED_COMMENT;
3419                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3420                         chattingPartner = savingComment - 3; // kludge to remember the box
3421                 } else {
3422                     started = STARTED_CHATTER;
3423                 }
3424                 continue;
3425             }
3426
3427             if (looking_at(buf, &i, "Black Strength :") ||
3428                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3429                 looking_at(buf, &i, "<10>") ||
3430                 looking_at(buf, &i, "#@#")) {
3431                 /* Wrong board style */
3432                 loggedOn = TRUE;
3433                 SendToICS(ics_prefix);
3434                 SendToICS("set style 12\n");
3435                 SendToICS(ics_prefix);
3436                 SendToICS("refresh\n");
3437                 continue;
3438             }
3439
3440             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3441                 ICSInitScript();
3442                 have_sent_ICS_logon = 1;
3443                 continue;
3444             }
3445
3446             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3447                 (looking_at(buf, &i, "\n<12> ") ||
3448                  looking_at(buf, &i, "<12> "))) {
3449                 loggedOn = TRUE;
3450                 if (oldi > next_out) {
3451                     SendToPlayer(&buf[next_out], oldi - next_out);
3452                 }
3453                 next_out = i;
3454                 started = STARTED_BOARD;
3455                 parse_pos = 0;
3456                 continue;
3457             }
3458
3459             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3460                 looking_at(buf, &i, "<b1> ")) {
3461                 if (oldi > next_out) {
3462                     SendToPlayer(&buf[next_out], oldi - next_out);
3463                 }
3464                 next_out = i;
3465                 started = STARTED_HOLDINGS;
3466                 parse_pos = 0;
3467                 continue;
3468             }
3469
3470             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3471                 loggedOn = TRUE;
3472                 /* Header for a move list -- first line */
3473
3474                 switch (ics_getting_history) {
3475                   case H_FALSE:
3476                     switch (gameMode) {
3477                       case IcsIdle:
3478                       case BeginningOfGame:
3479                         /* User typed "moves" or "oldmoves" while we
3480                            were idle.  Pretend we asked for these
3481                            moves and soak them up so user can step
3482                            through them and/or save them.
3483                            */
3484                         Reset(FALSE, TRUE);
3485                         gameMode = IcsObserving;
3486                         ModeHighlight();
3487                         ics_gamenum = -1;
3488                         ics_getting_history = H_GOT_UNREQ_HEADER;
3489                         break;
3490                       case EditGame: /*?*/
3491                       case EditPosition: /*?*/
3492                         /* Should above feature work in these modes too? */
3493                         /* For now it doesn't */
3494                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3495                         break;
3496                       default:
3497                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3498                         break;
3499                     }
3500                     break;
3501                   case H_REQUESTED:
3502                     /* Is this the right one? */
3503                     if (gameInfo.white && gameInfo.black &&
3504                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3505                         strcmp(gameInfo.black, star_match[2]) == 0) {
3506                         /* All is well */
3507                         ics_getting_history = H_GOT_REQ_HEADER;
3508                     }
3509                     break;
3510                   case H_GOT_REQ_HEADER:
3511                   case H_GOT_UNREQ_HEADER:
3512                   case H_GOT_UNWANTED_HEADER:
3513                   case H_GETTING_MOVES:
3514                     /* Should not happen */
3515                     DisplayError(_("Error gathering move list: two headers"), 0);
3516                     ics_getting_history = H_FALSE;
3517                     break;
3518                 }
3519
3520                 /* Save player ratings into gameInfo if needed */
3521                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3522                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3523                     (gameInfo.whiteRating == -1 ||
3524                      gameInfo.blackRating == -1)) {
3525
3526                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3527                     gameInfo.blackRating = string_to_rating(star_match[3]);
3528                     if (appData.debugMode)
3529                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3530                               gameInfo.whiteRating, gameInfo.blackRating);
3531                 }
3532                 continue;
3533             }
3534
3535             if (looking_at(buf, &i,
3536               "* * match, initial time: * minute*, increment: * second")) {
3537                 /* Header for a move list -- second line */
3538                 /* Initial board will follow if this is a wild game */
3539                 if (gameInfo.event != NULL) free(gameInfo.event);
3540                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3541                 gameInfo.event = StrSave(str);
3542                 /* [HGM] we switched variant. Translate boards if needed. */
3543                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3544                 continue;
3545             }
3546
3547             if (looking_at(buf, &i, "Move  ")) {
3548                 /* Beginning of a move list */
3549                 switch (ics_getting_history) {
3550                   case H_FALSE:
3551                     /* Normally should not happen */
3552                     /* Maybe user hit reset while we were parsing */
3553                     break;
3554                   case H_REQUESTED:
3555                     /* Happens if we are ignoring a move list that is not
3556                      * the one we just requested.  Common if the user
3557                      * tries to observe two games without turning off
3558                      * getMoveList */
3559                     break;
3560                   case H_GETTING_MOVES:
3561                     /* Should not happen */
3562                     DisplayError(_("Error gathering move list: nested"), 0);
3563                     ics_getting_history = H_FALSE;
3564                     break;
3565                   case H_GOT_REQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES;
3568                     parse_pos = 0;
3569                     if (oldi > next_out) {
3570                         SendToPlayer(&buf[next_out], oldi - next_out);
3571                     }
3572                     break;
3573                   case H_GOT_UNREQ_HEADER:
3574                     ics_getting_history = H_GETTING_MOVES;
3575                     started = STARTED_MOVES_NOHIDE;
3576                     parse_pos = 0;
3577                     break;
3578                   case H_GOT_UNWANTED_HEADER:
3579                     ics_getting_history = H_FALSE;
3580                     break;
3581                 }
3582                 continue;
3583             }
3584
3585             if (looking_at(buf, &i, "% ") ||
3586                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3587                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3588                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3589                     soughtPending = FALSE;
3590                     seekGraphUp = TRUE;
3591                     DrawSeekGraph();
3592                 }
3593                 if(suppressKibitz) next_out = i;
3594                 savingComment = FALSE;
3595                 suppressKibitz = 0;
3596                 switch (started) {
3597                   case STARTED_MOVES:
3598                   case STARTED_MOVES_NOHIDE:
3599                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3600                     parse[parse_pos + i - oldi] = NULLCHAR;
3601                     ParseGameHistory(parse);
3602 #if ZIPPY
3603                     if (appData.zippyPlay && first.initDone) {
3604                         FeedMovesToProgram(&first, forwardMostMove);
3605                         if (gameMode == IcsPlayingWhite) {
3606                             if (WhiteOnMove(forwardMostMove)) {
3607                                 if (first.sendTime) {
3608                                   if (first.useColors) {
3609                                     SendToProgram("black\n", &first);
3610                                   }
3611                                   SendTimeRemaining(&first, TRUE);
3612                                 }
3613                                 if (first.useColors) {
3614                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3615                                 }
3616                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3617                                 first.maybeThinking = TRUE;
3618                             } else {
3619                                 if (first.usePlayother) {
3620                                   if (first.sendTime) {
3621                                     SendTimeRemaining(&first, TRUE);
3622                                   }
3623                                   SendToProgram("playother\n", &first);
3624                                   firstMove = FALSE;
3625                                 } else {
3626                                   firstMove = TRUE;
3627                                 }
3628                             }
3629                         } else if (gameMode == IcsPlayingBlack) {
3630                             if (!WhiteOnMove(forwardMostMove)) {
3631                                 if (first.sendTime) {
3632                                   if (first.useColors) {
3633                                     SendToProgram("white\n", &first);
3634                                   }
3635                                   SendTimeRemaining(&first, FALSE);
3636                                 }
3637                                 if (first.useColors) {
3638                                   SendToProgram("black\n", &first);
3639                                 }
3640                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3641                                 first.maybeThinking = TRUE;
3642                             } else {
3643                                 if (first.usePlayother) {
3644                                   if (first.sendTime) {
3645                                     SendTimeRemaining(&first, FALSE);
3646                                   }
3647                                   SendToProgram("playother\n", &first);
3648                                   firstMove = FALSE;
3649                                 } else {
3650                                   firstMove = TRUE;
3651                                 }
3652                             }
3653                         }
3654                     }
3655 #endif
3656                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3657                         /* Moves came from oldmoves or moves command
3658                            while we weren't doing anything else.
3659                            */
3660                         currentMove = forwardMostMove;
3661                         ClearHighlights();/*!!could figure this out*/
3662                         flipView = appData.flipView;
3663                         DrawPosition(TRUE, boards[currentMove]);
3664                         DisplayBothClocks();
3665                         snprintf(str, MSG_SIZ, "%s %s %s",
3666                                 gameInfo.white, _("vs."),  gameInfo.black);
3667                         DisplayTitle(str);
3668                         gameMode = IcsIdle;
3669                     } else {
3670                         /* Moves were history of an active game */
3671                         if (gameInfo.resultDetails != NULL) {
3672                             free(gameInfo.resultDetails);
3673                             gameInfo.resultDetails = NULL;
3674                         }
3675                     }
3676                     HistorySet(parseList, backwardMostMove,
3677                                forwardMostMove, currentMove-1);
3678                     DisplayMove(currentMove - 1);
3679                     if (started == STARTED_MOVES) next_out = i;
3680                     started = STARTED_NONE;
3681                     ics_getting_history = H_FALSE;
3682                     break;
3683
3684                   case STARTED_OBSERVE:
3685                     started = STARTED_NONE;
3686                     SendToICS(ics_prefix);
3687                     SendToICS("refresh\n");
3688                     break;
3689
3690                   default:
3691                     break;
3692                 }
3693                 if(bookHit) { // [HGM] book: simulate book reply
3694                     static char bookMove[MSG_SIZ]; // a bit generous?
3695
3696                     programStats.nodes = programStats.depth = programStats.time =
3697                     programStats.score = programStats.got_only_move = 0;
3698                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3699
3700                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3701                     strcat(bookMove, bookHit);
3702                     HandleMachineMove(bookMove, &first);
3703                 }
3704                 continue;
3705             }
3706
3707             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3708                  started == STARTED_HOLDINGS ||
3709                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3710                 /* Accumulate characters in move list or board */
3711                 parse[parse_pos++] = buf[i];
3712             }
3713
3714             /* Start of game messages.  Mostly we detect start of game
3715                when the first board image arrives.  On some versions
3716                of the ICS, though, we need to do a "refresh" after starting
3717                to observe in order to get the current board right away. */
3718             if (looking_at(buf, &i, "Adding game * to observation list")) {
3719                 started = STARTED_OBSERVE;
3720                 continue;
3721             }
3722
3723             /* Handle auto-observe */
3724             if (appData.autoObserve &&
3725                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3726                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3727                 char *player;
3728                 /* Choose the player that was highlighted, if any. */
3729                 if (star_match[0][0] == '\033' ||
3730                     star_match[1][0] != '\033') {
3731                     player = star_match[0];
3732                 } else {
3733                     player = star_match[2];
3734                 }
3735                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3736                         ics_prefix, StripHighlightAndTitle(player));
3737                 SendToICS(str);
3738
3739                 /* Save ratings from notify string */
3740                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3741                 player1Rating = string_to_rating(star_match[1]);
3742                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3743                 player2Rating = string_to_rating(star_match[3]);
3744
3745                 if (appData.debugMode)
3746                   fprintf(debugFP,
3747                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3748                           player1Name, player1Rating,
3749                           player2Name, player2Rating);
3750
3751                 continue;
3752             }
3753
3754             /* Deal with automatic examine mode after a game,
3755                and with IcsObserving -> IcsExamining transition */
3756             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3757                 looking_at(buf, &i, "has made you an examiner of game *")) {
3758
3759                 int gamenum = atoi(star_match[0]);
3760                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3761                     gamenum == ics_gamenum) {
3762                     /* We were already playing or observing this game;
3763                        no need to refetch history */
3764                     gameMode = IcsExamining;
3765                     if (pausing) {
3766                         pauseExamForwardMostMove = forwardMostMove;
3767                     } else if (currentMove < forwardMostMove) {
3768                         ForwardInner(forwardMostMove);
3769                     }
3770                 } else {
3771                     /* I don't think this case really can happen */
3772                     SendToICS(ics_prefix);
3773                     SendToICS("refresh\n");
3774                 }
3775                 continue;
3776             }
3777
3778             /* Error messages */
3779 //          if (ics_user_moved) {
3780             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3781                 if (looking_at(buf, &i, "Illegal move") ||
3782                     looking_at(buf, &i, "Not a legal move") ||
3783                     looking_at(buf, &i, "Your king is in check") ||
3784                     looking_at(buf, &i, "It isn't your turn") ||
3785                     looking_at(buf, &i, "It is not your move")) {
3786                     /* Illegal move */
3787                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3788                         currentMove = forwardMostMove-1;
3789                         DisplayMove(currentMove - 1); /* before DMError */
3790                         DrawPosition(FALSE, boards[currentMove]);
3791                         SwitchClocks(forwardMostMove-1); // [HGM] race
3792                         DisplayBothClocks();
3793                     }
3794                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3795                     ics_user_moved = 0;
3796                     continue;
3797                 }
3798             }
3799
3800             if (looking_at(buf, &i, "still have time") ||
3801                 looking_at(buf, &i, "not out of time") ||
3802                 looking_at(buf, &i, "either player is out of time") ||
3803                 looking_at(buf, &i, "has timeseal; checking")) {
3804                 /* We must have called his flag a little too soon */
3805                 whiteFlag = blackFlag = FALSE;
3806                 continue;
3807             }
3808
3809             if (looking_at(buf, &i, "added * seconds to") ||
3810                 looking_at(buf, &i, "seconds were added to")) {
3811                 /* Update the clocks */
3812                 SendToICS(ics_prefix);
3813                 SendToICS("refresh\n");
3814                 continue;
3815             }
3816
3817             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3818                 ics_clock_paused = TRUE;
3819                 StopClocks();
3820                 continue;
3821             }
3822
3823             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3824                 ics_clock_paused = FALSE;
3825                 StartClocks();
3826                 continue;
3827             }
3828
3829             /* Grab player ratings from the Creating: message.
3830                Note we have to check for the special case when
3831                the ICS inserts things like [white] or [black]. */
3832             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3833                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3834                 /* star_matches:
3835                    0    player 1 name (not necessarily white)
3836                    1    player 1 rating
3837                    2    empty, white, or black (IGNORED)
3838                    3    player 2 name (not necessarily black)
3839                    4    player 2 rating
3840
3841                    The names/ratings are sorted out when the game
3842                    actually starts (below).
3843                 */
3844                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3845                 player1Rating = string_to_rating(star_match[1]);
3846                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3847                 player2Rating = string_to_rating(star_match[4]);
3848
3849                 if (appData.debugMode)
3850                   fprintf(debugFP,
3851                           "Ratings from 'Creating:' %s %d, %s %d\n",
3852                           player1Name, player1Rating,
3853                           player2Name, player2Rating);
3854
3855                 continue;
3856             }
3857
3858             /* Improved generic start/end-of-game messages */
3859             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3860                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3861                 /* If tkind == 0: */
3862                 /* star_match[0] is the game number */
3863                 /*           [1] is the white player's name */
3864                 /*           [2] is the black player's name */
3865                 /* For end-of-game: */
3866                 /*           [3] is the reason for the game end */
3867                 /*           [4] is a PGN end game-token, preceded by " " */
3868                 /* For start-of-game: */
3869                 /*           [3] begins with "Creating" or "Continuing" */
3870                 /*           [4] is " *" or empty (don't care). */
3871                 int gamenum = atoi(star_match[0]);
3872                 char *whitename, *blackname, *why, *endtoken;
3873                 ChessMove endtype = EndOfFile;
3874
3875                 if (tkind == 0) {
3876                   whitename = star_match[1];
3877                   blackname = star_match[2];
3878                   why = star_match[3];
3879                   endtoken = star_match[4];
3880                 } else {
3881                   whitename = star_match[1];
3882                   blackname = star_match[3];
3883                   why = star_match[5];
3884                   endtoken = star_match[6];
3885                 }
3886
3887                 /* Game start messages */
3888                 if (strncmp(why, "Creating ", 9) == 0 ||
3889                     strncmp(why, "Continuing ", 11) == 0) {
3890                     gs_gamenum = gamenum;
3891                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3892                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3893                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3894 #if ZIPPY
3895                     if (appData.zippyPlay) {
3896                         ZippyGameStart(whitename, blackname);
3897                     }
3898 #endif /*ZIPPY*/
3899                     partnerBoardValid = FALSE; // [HGM] bughouse
3900                     continue;
3901                 }
3902
3903                 /* Game end messages */
3904                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3905                     ics_gamenum != gamenum) {
3906                     continue;
3907                 }
3908                 while (endtoken[0] == ' ') endtoken++;
3909                 switch (endtoken[0]) {
3910                   case '*':
3911                   default:
3912                     endtype = GameUnfinished;
3913                     break;
3914                   case '0':
3915                     endtype = BlackWins;
3916                     break;
3917                   case '1':
3918                     if (endtoken[1] == '/')
3919                       endtype = GameIsDrawn;
3920                     else
3921                       endtype = WhiteWins;
3922                     break;
3923                 }
3924                 GameEnds(endtype, why, GE_ICS);
3925 #if ZIPPY
3926                 if (appData.zippyPlay && first.initDone) {
3927                     ZippyGameEnd(endtype, why);
3928                     if (first.pr == NoProc) {
3929                       /* Start the next process early so that we'll
3930                          be ready for the next challenge */
3931                       StartChessProgram(&first);
3932                     }
3933                     /* Send "new" early, in case this command takes
3934                        a long time to finish, so that we'll be ready
3935                        for the next challenge. */
3936                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3937                     Reset(TRUE, TRUE);
3938                 }
3939 #endif /*ZIPPY*/
3940                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3941                 continue;
3942             }
3943
3944             if (looking_at(buf, &i, "Removing game * from observation") ||
3945                 looking_at(buf, &i, "no longer observing game *") ||
3946                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3947                 if (gameMode == IcsObserving &&
3948                     atoi(star_match[0]) == ics_gamenum)
3949                   {
3950                       /* icsEngineAnalyze */
3951                       if (appData.icsEngineAnalyze) {
3952                             ExitAnalyzeMode();
3953                             ModeHighlight();
3954                       }
3955                       StopClocks();
3956                       gameMode = IcsIdle;
3957                       ics_gamenum = -1;
3958                       ics_user_moved = FALSE;
3959                   }
3960                 continue;
3961             }
3962
3963             if (looking_at(buf, &i, "no longer examining game *")) {
3964                 if (gameMode == IcsExamining &&
3965                     atoi(star_match[0]) == ics_gamenum)
3966                   {
3967                       gameMode = IcsIdle;
3968                       ics_gamenum = -1;
3969                       ics_user_moved = FALSE;
3970                   }
3971                 continue;
3972             }
3973
3974             /* Advance leftover_start past any newlines we find,
3975                so only partial lines can get reparsed */
3976             if (looking_at(buf, &i, "\n")) {
3977                 prevColor = curColor;
3978                 if (curColor != ColorNormal) {
3979                     if (oldi > next_out) {
3980                         SendToPlayer(&buf[next_out], oldi - next_out);
3981                         next_out = oldi;
3982                     }
3983                     Colorize(ColorNormal, FALSE);
3984                     curColor = ColorNormal;
3985                 }
3986                 if (started == STARTED_BOARD) {
3987                     started = STARTED_NONE;
3988                     parse[parse_pos] = NULLCHAR;
3989                     ParseBoard12(parse);
3990                     ics_user_moved = 0;
3991
3992                     /* Send premove here */
3993                     if (appData.premove) {
3994                       char str[MSG_SIZ];
3995                       if (currentMove == 0 &&
3996                           gameMode == IcsPlayingWhite &&
3997                           appData.premoveWhite) {
3998                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3999                         if (appData.debugMode)
4000                           fprintf(debugFP, "Sending premove:\n");
4001                         SendToICS(str);
4002                       } else if (currentMove == 1 &&
4003                                  gameMode == IcsPlayingBlack &&
4004                                  appData.premoveBlack) {
4005                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4006                         if (appData.debugMode)
4007                           fprintf(debugFP, "Sending premove:\n");
4008                         SendToICS(str);
4009                       } else if (gotPremove) {
4010                         gotPremove = 0;
4011                         ClearPremoveHighlights();
4012                         if (appData.debugMode)
4013                           fprintf(debugFP, "Sending premove:\n");
4014                           UserMoveEvent(premoveFromX, premoveFromY,
4015                                         premoveToX, premoveToY,
4016                                         premovePromoChar);
4017                       }
4018                     }
4019
4020                     /* Usually suppress following prompt */
4021                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4022                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4023                         if (looking_at(buf, &i, "*% ")) {
4024                             savingComment = FALSE;
4025                             suppressKibitz = 0;
4026                         }
4027                     }
4028                     next_out = i;
4029                 } else if (started == STARTED_HOLDINGS) {
4030                     int gamenum;
4031                     char new_piece[MSG_SIZ];
4032                     started = STARTED_NONE;
4033                     parse[parse_pos] = NULLCHAR;
4034                     if (appData.debugMode)
4035                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4036                                                         parse, currentMove);
4037                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4038                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4039                         if (gameInfo.variant == VariantNormal) {
4040                           /* [HGM] We seem to switch variant during a game!
4041                            * Presumably no holdings were displayed, so we have
4042                            * to move the position two files to the right to
4043                            * create room for them!
4044                            */
4045                           VariantClass newVariant;
4046                           switch(gameInfo.boardWidth) { // base guess on board width
4047                                 case 9:  newVariant = VariantShogi; break;
4048                                 case 10: newVariant = VariantGreat; break;
4049                                 default: newVariant = VariantCrazyhouse; break;
4050                           }
4051                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4052                           /* Get a move list just to see the header, which
4053                              will tell us whether this is really bug or zh */
4054                           if (ics_getting_history == H_FALSE) {
4055                             ics_getting_history = H_REQUESTED;
4056                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4057                             SendToICS(str);
4058                           }
4059                         }
4060                         new_piece[0] = NULLCHAR;
4061                         sscanf(parse, "game %d white [%s black [%s <- %s",
4062                                &gamenum, white_holding, black_holding,
4063                                new_piece);
4064                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4065                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4066                         /* [HGM] copy holdings to board holdings area */
4067                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4068                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4069                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4070 #if ZIPPY
4071                         if (appData.zippyPlay && first.initDone) {
4072                             ZippyHoldings(white_holding, black_holding,
4073                                           new_piece);
4074                         }
4075 #endif /*ZIPPY*/
4076                         if (tinyLayout || smallLayout) {
4077                             char wh[16], bh[16];
4078                             PackHolding(wh, white_holding);
4079                             PackHolding(bh, black_holding);
4080                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4081                                     gameInfo.white, gameInfo.black);
4082                         } else {
4083                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4084                                     gameInfo.white, white_holding, _("vs."),
4085                                     gameInfo.black, black_holding);
4086                         }
4087                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4088                         DrawPosition(FALSE, boards[currentMove]);
4089                         DisplayTitle(str);
4090                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4091                         sscanf(parse, "game %d white [%s black [%s <- %s",
4092                                &gamenum, white_holding, black_holding,
4093                                new_piece);
4094                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4095                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4096                         /* [HGM] copy holdings to partner-board holdings area */
4097                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4098                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4099                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4100                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4101                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4102                       }
4103                     }
4104                     /* Suppress following prompt */
4105                     if (looking_at(buf, &i, "*% ")) {
4106                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4107                         savingComment = FALSE;
4108                         suppressKibitz = 0;
4109                     }
4110                     next_out = i;
4111                 }
4112                 continue;
4113             }
4114
4115             i++;                /* skip unparsed character and loop back */
4116         }
4117
4118         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4119 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4120 //          SendToPlayer(&buf[next_out], i - next_out);
4121             started != STARTED_HOLDINGS && leftover_start > next_out) {
4122             SendToPlayer(&buf[next_out], leftover_start - next_out);
4123             next_out = i;
4124         }
4125
4126         leftover_len = buf_len - leftover_start;
4127         /* if buffer ends with something we couldn't parse,
4128            reparse it after appending the next read */
4129
4130     } else if (count == 0) {
4131         RemoveInputSource(isr);
4132         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4133     } else {
4134         DisplayFatalError(_("Error reading from ICS"), error, 1);
4135     }
4136 }
4137
4138
4139 /* Board style 12 looks like this:
4140
4141    <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
4142
4143  * The "<12> " is stripped before it gets to this routine.  The two
4144  * trailing 0's (flip state and clock ticking) are later addition, and
4145  * some chess servers may not have them, or may have only the first.
4146  * Additional trailing fields may be added in the future.
4147  */
4148
4149 #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"
4150
4151 #define RELATION_OBSERVING_PLAYED    0
4152 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4153 #define RELATION_PLAYING_MYMOVE      1
4154 #define RELATION_PLAYING_NOTMYMOVE  -1
4155 #define RELATION_EXAMINING           2
4156 #define RELATION_ISOLATED_BOARD     -3
4157 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4158
4159 void
4160 ParseBoard12 (char *string)
4161 {
4162     GameMode newGameMode;
4163     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4164     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4165     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4166     char to_play, board_chars[200];
4167     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4168     char black[32], white[32];
4169     Board board;
4170     int prevMove = currentMove;
4171     int ticking = 2;
4172     ChessMove moveType;
4173     int fromX, fromY, toX, toY;
4174     char promoChar;
4175     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4176     char *bookHit = NULL; // [HGM] book
4177     Boolean weird = FALSE, reqFlag = FALSE;
4178
4179     fromX = fromY = toX = toY = -1;
4180
4181     newGame = FALSE;
4182
4183     if (appData.debugMode)
4184       fprintf(debugFP, _("Parsing board: %s\n"), string);
4185
4186     move_str[0] = NULLCHAR;
4187     elapsed_time[0] = NULLCHAR;
4188     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4189         int  i = 0, j;
4190         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4191             if(string[i] == ' ') { ranks++; files = 0; }
4192             else files++;
4193             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4194             i++;
4195         }
4196         for(j = 0; j <i; j++) board_chars[j] = string[j];
4197         board_chars[i] = '\0';
4198         string += i + 1;
4199     }
4200     n = sscanf(string, PATTERN, &to_play, &double_push,
4201                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4202                &gamenum, white, black, &relation, &basetime, &increment,
4203                &white_stren, &black_stren, &white_time, &black_time,
4204                &moveNum, str, elapsed_time, move_str, &ics_flip,
4205                &ticking);
4206
4207     if (n < 21) {
4208         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4209         DisplayError(str, 0);
4210         return;
4211     }
4212
4213     /* Convert the move number to internal form */
4214     moveNum = (moveNum - 1) * 2;
4215     if (to_play == 'B') moveNum++;
4216     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4217       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4218                         0, 1);
4219       return;
4220     }
4221
4222     switch (relation) {
4223       case RELATION_OBSERVING_PLAYED:
4224       case RELATION_OBSERVING_STATIC:
4225         if (gamenum == -1) {
4226             /* Old ICC buglet */
4227             relation = RELATION_OBSERVING_STATIC;
4228         }
4229         newGameMode = IcsObserving;
4230         break;
4231       case RELATION_PLAYING_MYMOVE:
4232       case RELATION_PLAYING_NOTMYMOVE:
4233         newGameMode =
4234           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4235             IcsPlayingWhite : IcsPlayingBlack;
4236         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4237         break;
4238       case RELATION_EXAMINING:
4239         newGameMode = IcsExamining;
4240         break;
4241       case RELATION_ISOLATED_BOARD:
4242       default:
4243         /* Just display this board.  If user was doing something else,
4244            we will forget about it until the next board comes. */
4245         newGameMode = IcsIdle;
4246         break;
4247       case RELATION_STARTING_POSITION:
4248         newGameMode = gameMode;
4249         break;
4250     }
4251
4252     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4253         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4254          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4255       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4256       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4257       static int lastBgGame = -1;
4258       char *toSqr;
4259       for (k = 0; k < ranks; k++) {
4260         for (j = 0; j < files; j++)
4261           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4262         if(gameInfo.holdingsWidth > 1) {
4263              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4264              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4265         }
4266       }
4267       CopyBoard(partnerBoard, board);
4268       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4269         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4270         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4271       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4272       if(toSqr = strchr(str, '-')) {
4273         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4274         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4275       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4276       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4277       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4278       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4279       if(twoBoards) {
4280           DisplayWhiteClock(white_time*fac, to_play == 'W');
4281           DisplayBlackClock(black_time*fac, to_play != 'W');
4282           activePartner = to_play;
4283           if(gamenum != lastBgGame) {
4284               char buf[MSG_SIZ];
4285               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4286               DisplayTitle(buf);
4287           }
4288           lastBgGame = gamenum;
4289           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4290                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4291       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4292                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4293       DisplayMessage(partnerStatus, "");
4294         partnerBoardValid = TRUE;
4295       return;
4296     }
4297
4298     if(appData.dualBoard && appData.bgObserve) {
4299         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4300             SendToICS(ics_prefix), SendToICS("pobserve\n");
4301         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4302             char buf[MSG_SIZ];
4303             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4304             SendToICS(buf);
4305         }
4306     }
4307
4308     /* Modify behavior for initial board display on move listing
4309        of wild games.
4310        */
4311     switch (ics_getting_history) {
4312       case H_FALSE:
4313       case H_REQUESTED:
4314         break;
4315       case H_GOT_REQ_HEADER:
4316       case H_GOT_UNREQ_HEADER:
4317         /* This is the initial position of the current game */
4318         gamenum = ics_gamenum;
4319         moveNum = 0;            /* old ICS bug workaround */
4320         if (to_play == 'B') {
4321           startedFromSetupPosition = TRUE;
4322           blackPlaysFirst = TRUE;
4323           moveNum = 1;
4324           if (forwardMostMove == 0) forwardMostMove = 1;
4325           if (backwardMostMove == 0) backwardMostMove = 1;
4326           if (currentMove == 0) currentMove = 1;
4327         }
4328         newGameMode = gameMode;
4329         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4330         break;
4331       case H_GOT_UNWANTED_HEADER:
4332         /* This is an initial board that we don't want */
4333         return;
4334       case H_GETTING_MOVES:
4335         /* Should not happen */
4336         DisplayError(_("Error gathering move list: extra board"), 0);
4337         ics_getting_history = H_FALSE;
4338         return;
4339     }
4340
4341    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4342                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4343                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4344      /* [HGM] We seem to have switched variant unexpectedly
4345       * Try to guess new variant from board size
4346       */
4347           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4348           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4349           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4350           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4351           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4352           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4353           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4354           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4355           /* Get a move list just to see the header, which
4356              will tell us whether this is really bug or zh */
4357           if (ics_getting_history == H_FALSE) {
4358             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4359             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4360             SendToICS(str);
4361           }
4362     }
4363
4364     /* Take action if this is the first board of a new game, or of a
4365        different game than is currently being displayed.  */
4366     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4367         relation == RELATION_ISOLATED_BOARD) {
4368
4369         /* Forget the old game and get the history (if any) of the new one */
4370         if (gameMode != BeginningOfGame) {
4371           Reset(TRUE, TRUE);
4372         }
4373         newGame = TRUE;
4374         if (appData.autoRaiseBoard) BoardToTop();
4375         prevMove = -3;
4376         if (gamenum == -1) {
4377             newGameMode = IcsIdle;
4378         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4379                    appData.getMoveList && !reqFlag) {
4380             /* Need to get game history */
4381             ics_getting_history = H_REQUESTED;
4382             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4383             SendToICS(str);
4384         }
4385
4386         /* Initially flip the board to have black on the bottom if playing
4387            black or if the ICS flip flag is set, but let the user change
4388            it with the Flip View button. */
4389         flipView = appData.autoFlipView ?
4390           (newGameMode == IcsPlayingBlack) || ics_flip :
4391           appData.flipView;
4392
4393         /* Done with values from previous mode; copy in new ones */
4394         gameMode = newGameMode;
4395         ModeHighlight();
4396         ics_gamenum = gamenum;
4397         if (gamenum == gs_gamenum) {
4398             int klen = strlen(gs_kind);
4399             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4400             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4401             gameInfo.event = StrSave(str);
4402         } else {
4403             gameInfo.event = StrSave("ICS game");
4404         }
4405         gameInfo.site = StrSave(appData.icsHost);
4406         gameInfo.date = PGNDate();
4407         gameInfo.round = StrSave("-");
4408         gameInfo.white = StrSave(white);
4409         gameInfo.black = StrSave(black);
4410         timeControl = basetime * 60 * 1000;
4411         timeControl_2 = 0;
4412         timeIncrement = increment * 1000;
4413         movesPerSession = 0;
4414         gameInfo.timeControl = TimeControlTagValue();
4415         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4416   if (appData.debugMode) {
4417     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4418     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4419     setbuf(debugFP, NULL);
4420   }
4421
4422         gameInfo.outOfBook = NULL;
4423
4424         /* Do we have the ratings? */
4425         if (strcmp(player1Name, white) == 0 &&
4426             strcmp(player2Name, black) == 0) {
4427             if (appData.debugMode)
4428               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4429                       player1Rating, player2Rating);
4430             gameInfo.whiteRating = player1Rating;
4431             gameInfo.blackRating = player2Rating;
4432         } else if (strcmp(player2Name, white) == 0 &&
4433                    strcmp(player1Name, black) == 0) {
4434             if (appData.debugMode)
4435               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4436                       player2Rating, player1Rating);
4437             gameInfo.whiteRating = player2Rating;
4438             gameInfo.blackRating = player1Rating;
4439         }
4440         player1Name[0] = player2Name[0] = NULLCHAR;
4441
4442         /* Silence shouts if requested */
4443         if (appData.quietPlay &&
4444             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4445             SendToICS(ics_prefix);
4446             SendToICS("set shout 0\n");
4447         }
4448     }
4449
4450     /* Deal with midgame name changes */
4451     if (!newGame) {
4452         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4453             if (gameInfo.white) free(gameInfo.white);
4454             gameInfo.white = StrSave(white);
4455         }
4456         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4457             if (gameInfo.black) free(gameInfo.black);
4458             gameInfo.black = StrSave(black);
4459         }
4460     }
4461
4462     /* Throw away game result if anything actually changes in examine mode */
4463     if (gameMode == IcsExamining && !newGame) {
4464         gameInfo.result = GameUnfinished;
4465         if (gameInfo.resultDetails != NULL) {
4466             free(gameInfo.resultDetails);
4467             gameInfo.resultDetails = NULL;
4468         }
4469     }
4470
4471     /* In pausing && IcsExamining mode, we ignore boards coming
4472        in if they are in a different variation than we are. */
4473     if (pauseExamInvalid) return;
4474     if (pausing && gameMode == IcsExamining) {
4475         if (moveNum <= pauseExamForwardMostMove) {
4476             pauseExamInvalid = TRUE;
4477             forwardMostMove = pauseExamForwardMostMove;
4478             return;
4479         }
4480     }
4481
4482   if (appData.debugMode) {
4483     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4484   }
4485     /* Parse the board */
4486     for (k = 0; k < ranks; k++) {
4487       for (j = 0; j < files; j++)
4488         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4489       if(gameInfo.holdingsWidth > 1) {
4490            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4491            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4492       }
4493     }
4494     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4495       board[5][BOARD_RGHT+1] = WhiteAngel;
4496       board[6][BOARD_RGHT+1] = WhiteMarshall;
4497       board[1][0] = BlackMarshall;
4498       board[2][0] = BlackAngel;
4499       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4500     }
4501     CopyBoard(boards[moveNum], board);
4502     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4503     if (moveNum == 0) {
4504         startedFromSetupPosition =
4505           !CompareBoards(board, initialPosition);
4506         if(startedFromSetupPosition)
4507             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4508     }
4509
4510     /* [HGM] Set castling rights. Take the outermost Rooks,
4511        to make it also work for FRC opening positions. Note that board12
4512        is really defective for later FRC positions, as it has no way to
4513        indicate which Rook can castle if they are on the same side of King.
4514        For the initial position we grant rights to the outermost Rooks,
4515        and remember thos rights, and we then copy them on positions
4516        later in an FRC game. This means WB might not recognize castlings with
4517        Rooks that have moved back to their original position as illegal,
4518        but in ICS mode that is not its job anyway.
4519     */
4520     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4521     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4522
4523         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4524             if(board[0][i] == WhiteRook) j = i;
4525         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4526         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4527             if(board[0][i] == WhiteRook) j = i;
4528         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4529         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4530             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4531         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4532         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4533             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4534         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4535
4536         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4537         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4538         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4539             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4540         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4541             if(board[BOARD_HEIGHT-1][k] == bKing)
4542                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4543         if(gameInfo.variant == VariantTwoKings) {
4544             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4545             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4546             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4547         }
4548     } else { int r;
4549         r = boards[moveNum][CASTLING][0] = initialRights[0];
4550         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4551         r = boards[moveNum][CASTLING][1] = initialRights[1];
4552         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4553         r = boards[moveNum][CASTLING][3] = initialRights[3];
4554         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4555         r = boards[moveNum][CASTLING][4] = initialRights[4];
4556         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4557         /* wildcastle kludge: always assume King has rights */
4558         r = boards[moveNum][CASTLING][2] = initialRights[2];
4559         r = boards[moveNum][CASTLING][5] = initialRights[5];
4560     }
4561     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4562     boards[moveNum][EP_STATUS] = EP_NONE;
4563     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4564     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4565     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4566
4567
4568     if (ics_getting_history == H_GOT_REQ_HEADER ||
4569         ics_getting_history == H_GOT_UNREQ_HEADER) {
4570         /* This was an initial position from a move list, not
4571            the current position */
4572         return;
4573     }
4574
4575     /* Update currentMove and known move number limits */
4576     newMove = newGame || moveNum > forwardMostMove;
4577
4578     if (newGame) {
4579         forwardMostMove = backwardMostMove = currentMove = moveNum;
4580         if (gameMode == IcsExamining && moveNum == 0) {
4581           /* Workaround for ICS limitation: we are not told the wild
4582              type when starting to examine a game.  But if we ask for
4583              the move list, the move list header will tell us */
4584             ics_getting_history = H_REQUESTED;
4585             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4586             SendToICS(str);
4587         }
4588     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4589                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4590 #if ZIPPY
4591         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4592         /* [HGM] applied this also to an engine that is silently watching        */
4593         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4594             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4595             gameInfo.variant == currentlyInitializedVariant) {
4596           takeback = forwardMostMove - moveNum;
4597           for (i = 0; i < takeback; i++) {
4598             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4599             SendToProgram("undo\n", &first);
4600           }
4601         }
4602 #endif
4603
4604         forwardMostMove = moveNum;
4605         if (!pausing || currentMove > forwardMostMove)
4606           currentMove = forwardMostMove;
4607     } else {
4608         /* New part of history that is not contiguous with old part */
4609         if (pausing && gameMode == IcsExamining) {
4610             pauseExamInvalid = TRUE;
4611             forwardMostMove = pauseExamForwardMostMove;
4612             return;
4613         }
4614         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4615 #if ZIPPY
4616             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4617                 // [HGM] when we will receive the move list we now request, it will be
4618                 // fed to the engine from the first move on. So if the engine is not
4619                 // in the initial position now, bring it there.
4620                 InitChessProgram(&first, 0);
4621             }
4622 #endif
4623             ics_getting_history = H_REQUESTED;
4624             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4625             SendToICS(str);
4626         }
4627         forwardMostMove = backwardMostMove = currentMove = moveNum;
4628     }
4629
4630     /* Update the clocks */
4631     if (strchr(elapsed_time, '.')) {
4632       /* Time is in ms */
4633       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4634       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4635     } else {
4636       /* Time is in seconds */
4637       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4638       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4639     }
4640
4641
4642 #if ZIPPY
4643     if (appData.zippyPlay && newGame &&
4644         gameMode != IcsObserving && gameMode != IcsIdle &&
4645         gameMode != IcsExamining)
4646       ZippyFirstBoard(moveNum, basetime, increment);
4647 #endif
4648
4649     /* Put the move on the move list, first converting
4650        to canonical algebraic form. */
4651     if (moveNum > 0) {
4652   if (appData.debugMode) {
4653     if (appData.debugMode) { int f = forwardMostMove;
4654         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4655                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4656                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4657     }
4658     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4659     fprintf(debugFP, "moveNum = %d\n", moveNum);
4660     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4661     setbuf(debugFP, NULL);
4662   }
4663         if (moveNum <= backwardMostMove) {
4664             /* We don't know what the board looked like before
4665                this move.  Punt. */
4666           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4667             strcat(parseList[moveNum - 1], " ");
4668             strcat(parseList[moveNum - 1], elapsed_time);
4669             moveList[moveNum - 1][0] = NULLCHAR;
4670         } else if (strcmp(move_str, "none") == 0) {
4671             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4672             /* Again, we don't know what the board looked like;
4673                this is really the start of the game. */
4674             parseList[moveNum - 1][0] = NULLCHAR;
4675             moveList[moveNum - 1][0] = NULLCHAR;
4676             backwardMostMove = moveNum;
4677             startedFromSetupPosition = TRUE;
4678             fromX = fromY = toX = toY = -1;
4679         } else {
4680           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4681           //                 So we parse the long-algebraic move string in stead of the SAN move
4682           int valid; char buf[MSG_SIZ], *prom;
4683
4684           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4685                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4686           // str looks something like "Q/a1-a2"; kill the slash
4687           if(str[1] == '/')
4688             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4689           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4690           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4691                 strcat(buf, prom); // long move lacks promo specification!
4692           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4693                 if(appData.debugMode)
4694                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4695                 safeStrCpy(move_str, buf, MSG_SIZ);
4696           }
4697           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4698                                 &fromX, &fromY, &toX, &toY, &promoChar)
4699                || ParseOneMove(buf, moveNum - 1, &moveType,
4700                                 &fromX, &fromY, &toX, &toY, &promoChar);
4701           // end of long SAN patch
4702           if (valid) {
4703             (void) CoordsToAlgebraic(boards[moveNum - 1],
4704                                      PosFlags(moveNum - 1),
4705                                      fromY, fromX, toY, toX, promoChar,
4706                                      parseList[moveNum-1]);
4707             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4708               case MT_NONE:
4709               case MT_STALEMATE:
4710               default:
4711                 break;
4712               case MT_CHECK:
4713                 if(gameInfo.variant != VariantShogi)
4714                     strcat(parseList[moveNum - 1], "+");
4715                 break;
4716               case MT_CHECKMATE:
4717               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4718                 strcat(parseList[moveNum - 1], "#");
4719                 break;
4720             }
4721             strcat(parseList[moveNum - 1], " ");
4722             strcat(parseList[moveNum - 1], elapsed_time);
4723             /* currentMoveString is set as a side-effect of ParseOneMove */
4724             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4725             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4726             strcat(moveList[moveNum - 1], "\n");
4727
4728             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4729                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4730               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4731                 ChessSquare old, new = boards[moveNum][k][j];
4732                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4733                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4734                   if(old == new) continue;
4735                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4736                   else if(new == WhiteWazir || new == BlackWazir) {
4737                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4738                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4739                       else boards[moveNum][k][j] = old; // preserve type of Gold
4740                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4741                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4742               }
4743           } else {
4744             /* Move from ICS was illegal!?  Punt. */
4745             if (appData.debugMode) {
4746               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4747               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4748             }
4749             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4750             strcat(parseList[moveNum - 1], " ");
4751             strcat(parseList[moveNum - 1], elapsed_time);
4752             moveList[moveNum - 1][0] = NULLCHAR;
4753             fromX = fromY = toX = toY = -1;
4754           }
4755         }
4756   if (appData.debugMode) {
4757     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4758     setbuf(debugFP, NULL);
4759   }
4760
4761 #if ZIPPY
4762         /* Send move to chess program (BEFORE animating it). */
4763         if (appData.zippyPlay && !newGame && newMove &&
4764            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4765
4766             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4767                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4768                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4769                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4770                             move_str);
4771                     DisplayError(str, 0);
4772                 } else {
4773                     if (first.sendTime) {
4774                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4775                     }
4776                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4777                     if (firstMove && !bookHit) {
4778                         firstMove = FALSE;
4779                         if (first.useColors) {
4780                           SendToProgram(gameMode == IcsPlayingWhite ?
4781                                         "white\ngo\n" :
4782                                         "black\ngo\n", &first);
4783                         } else {
4784                           SendToProgram("go\n", &first);
4785                         }
4786                         first.maybeThinking = TRUE;
4787                     }
4788                 }
4789             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4790               if (moveList[moveNum - 1][0] == NULLCHAR) {
4791                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4792                 DisplayError(str, 0);
4793               } else {
4794                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4795                 SendMoveToProgram(moveNum - 1, &first);
4796               }
4797             }
4798         }
4799 #endif
4800     }
4801
4802     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4803         /* If move comes from a remote source, animate it.  If it
4804            isn't remote, it will have already been animated. */
4805         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4806             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4807         }
4808         if (!pausing && appData.highlightLastMove) {
4809             SetHighlights(fromX, fromY, toX, toY);
4810         }
4811     }
4812
4813     /* Start the clocks */
4814     whiteFlag = blackFlag = FALSE;
4815     appData.clockMode = !(basetime == 0 && increment == 0);
4816     if (ticking == 0) {
4817       ics_clock_paused = TRUE;
4818       StopClocks();
4819     } else if (ticking == 1) {
4820       ics_clock_paused = FALSE;
4821     }
4822     if (gameMode == IcsIdle ||
4823         relation == RELATION_OBSERVING_STATIC ||
4824         relation == RELATION_EXAMINING ||
4825         ics_clock_paused)
4826       DisplayBothClocks();
4827     else
4828       StartClocks();
4829
4830     /* Display opponents and material strengths */
4831     if (gameInfo.variant != VariantBughouse &&
4832         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4833         if (tinyLayout || smallLayout) {
4834             if(gameInfo.variant == VariantNormal)
4835               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4836                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4837                     basetime, increment);
4838             else
4839               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4840                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4841                     basetime, increment, (int) gameInfo.variant);
4842         } else {
4843             if(gameInfo.variant == VariantNormal)
4844               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4845                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4846                     basetime, increment);
4847             else
4848               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4849                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4850                     basetime, increment, VariantName(gameInfo.variant));
4851         }
4852         DisplayTitle(str);
4853   if (appData.debugMode) {
4854     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4855   }
4856     }
4857
4858
4859     /* Display the board */
4860     if (!pausing && !appData.noGUI) {
4861
4862       if (appData.premove)
4863           if (!gotPremove ||
4864              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4865              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4866               ClearPremoveHighlights();
4867
4868       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4869         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4870       DrawPosition(j, boards[currentMove]);
4871
4872       DisplayMove(moveNum - 1);
4873       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4874             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4875               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4876         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4877       }
4878     }
4879
4880     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4881 #if ZIPPY
4882     if(bookHit) { // [HGM] book: simulate book reply
4883         static char bookMove[MSG_SIZ]; // a bit generous?
4884
4885         programStats.nodes = programStats.depth = programStats.time =
4886         programStats.score = programStats.got_only_move = 0;
4887         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4888
4889         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4890         strcat(bookMove, bookHit);
4891         HandleMachineMove(bookMove, &first);
4892     }
4893 #endif
4894 }
4895
4896 void
4897 GetMoveListEvent ()
4898 {
4899     char buf[MSG_SIZ];
4900     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4901         ics_getting_history = H_REQUESTED;
4902         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4903         SendToICS(buf);
4904     }
4905 }
4906
4907 void
4908 SendToBoth (char *msg)
4909 {   // to make it easy to keep two engines in step in dual analysis
4910     SendToProgram(msg, &first);
4911     if(second.analyzing) SendToProgram(msg, &second);
4912 }
4913
4914 void
4915 AnalysisPeriodicEvent (int force)
4916 {
4917     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4918          && !force) || !appData.periodicUpdates)
4919       return;
4920
4921     /* Send . command to Crafty to collect stats */
4922     SendToBoth(".\n");
4923
4924     /* Don't send another until we get a response (this makes
4925        us stop sending to old Crafty's which don't understand
4926        the "." command (sending illegal cmds resets node count & time,
4927        which looks bad)) */
4928     programStats.ok_to_send = 0;
4929 }
4930
4931 void
4932 ics_update_width (int new_width)
4933 {
4934         ics_printf("set width %d\n", new_width);
4935 }
4936
4937 void
4938 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4939 {
4940     char buf[MSG_SIZ];
4941
4942     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4943         // null move in variant where engine does not understand it (for analysis purposes)
4944         SendBoard(cps, moveNum + 1); // send position after move in stead.
4945         return;
4946     }
4947     if (cps->useUsermove) {
4948       SendToProgram("usermove ", cps);
4949     }
4950     if (cps->useSAN) {
4951       char *space;
4952       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4953         int len = space - parseList[moveNum];
4954         memcpy(buf, parseList[moveNum], len);
4955         buf[len++] = '\n';
4956         buf[len] = NULLCHAR;
4957       } else {
4958         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4959       }
4960       SendToProgram(buf, cps);
4961     } else {
4962       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4963         AlphaRank(moveList[moveNum], 4);
4964         SendToProgram(moveList[moveNum], cps);
4965         AlphaRank(moveList[moveNum], 4); // and back
4966       } else
4967       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4968        * the engine. It would be nice to have a better way to identify castle
4969        * moves here. */
4970       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4971                                                                          && cps->useOOCastle) {
4972         int fromX = moveList[moveNum][0] - AAA;
4973         int fromY = moveList[moveNum][1] - ONE;
4974         int toX = moveList[moveNum][2] - AAA;
4975         int toY = moveList[moveNum][3] - ONE;
4976         if((boards[moveNum][fromY][fromX] == WhiteKing
4977             && boards[moveNum][toY][toX] == WhiteRook)
4978            || (boards[moveNum][fromY][fromX] == BlackKing
4979                && boards[moveNum][toY][toX] == BlackRook)) {
4980           if(toX > fromX) SendToProgram("O-O\n", cps);
4981           else SendToProgram("O-O-O\n", cps);
4982         }
4983         else SendToProgram(moveList[moveNum], cps);
4984       } else
4985       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4986         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4987           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4988           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4989                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4990         } else
4991           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4992                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4993         SendToProgram(buf, cps);
4994       }
4995       else SendToProgram(moveList[moveNum], cps);
4996       /* End of additions by Tord */
4997     }
4998
4999     /* [HGM] setting up the opening has brought engine in force mode! */
5000     /*       Send 'go' if we are in a mode where machine should play. */
5001     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5002         (gameMode == TwoMachinesPlay   ||
5003 #if ZIPPY
5004          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5005 #endif
5006          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5007         SendToProgram("go\n", cps);
5008   if (appData.debugMode) {
5009     fprintf(debugFP, "(extra)\n");
5010   }
5011     }
5012     setboardSpoiledMachineBlack = 0;
5013 }
5014
5015 void
5016 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5017 {
5018     char user_move[MSG_SIZ];
5019     char suffix[4];
5020
5021     if(gameInfo.variant == VariantSChess && promoChar) {
5022         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5023         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5024     } else suffix[0] = NULLCHAR;
5025
5026     switch (moveType) {
5027       default:
5028         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5029                 (int)moveType, fromX, fromY, toX, toY);
5030         DisplayError(user_move + strlen("say "), 0);
5031         break;
5032       case WhiteKingSideCastle:
5033       case BlackKingSideCastle:
5034       case WhiteQueenSideCastleWild:
5035       case BlackQueenSideCastleWild:
5036       /* PUSH Fabien */
5037       case WhiteHSideCastleFR:
5038       case BlackHSideCastleFR:
5039       /* POP Fabien */
5040         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5041         break;
5042       case WhiteQueenSideCastle:
5043       case BlackQueenSideCastle:
5044       case WhiteKingSideCastleWild:
5045       case BlackKingSideCastleWild:
5046       /* PUSH Fabien */
5047       case WhiteASideCastleFR:
5048       case BlackASideCastleFR:
5049       /* POP Fabien */
5050         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5051         break;
5052       case WhiteNonPromotion:
5053       case BlackNonPromotion:
5054         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5055         break;
5056       case WhitePromotion:
5057       case BlackPromotion:
5058         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5059           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5060                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5061                 PieceToChar(WhiteFerz));
5062         else if(gameInfo.variant == VariantGreat)
5063           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5064                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5065                 PieceToChar(WhiteMan));
5066         else
5067           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5069                 promoChar);
5070         break;
5071       case WhiteDrop:
5072       case BlackDrop:
5073       drop:
5074         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5075                  ToUpper(PieceToChar((ChessSquare) fromX)),
5076                  AAA + toX, ONE + toY);
5077         break;
5078       case IllegalMove:  /* could be a variant we don't quite understand */
5079         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5080       case NormalMove:
5081       case WhiteCapturesEnPassant:
5082       case BlackCapturesEnPassant:
5083         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5084                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5085         break;
5086     }
5087     SendToICS(user_move);
5088     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5089         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5090 }
5091
5092 void
5093 UploadGameEvent ()
5094 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5095     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5096     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5097     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5098       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5099       return;
5100     }
5101     if(gameMode != IcsExamining) { // is this ever not the case?
5102         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5103
5104         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5105           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5106         } else { // on FICS we must first go to general examine mode
5107           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5108         }
5109         if(gameInfo.variant != VariantNormal) {
5110             // try figure out wild number, as xboard names are not always valid on ICS
5111             for(i=1; i<=36; i++) {
5112               snprintf(buf, MSG_SIZ, "wild/%d", i);
5113                 if(StringToVariant(buf) == gameInfo.variant) break;
5114             }
5115             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5116             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5117             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5118         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5119         SendToICS(ics_prefix);
5120         SendToICS(buf);
5121         if(startedFromSetupPosition || backwardMostMove != 0) {
5122           fen = PositionToFEN(backwardMostMove, NULL);
5123           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5124             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5125             SendToICS(buf);
5126           } else { // FICS: everything has to set by separate bsetup commands
5127             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5128             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5129             SendToICS(buf);
5130             if(!WhiteOnMove(backwardMostMove)) {
5131                 SendToICS("bsetup tomove black\n");
5132             }
5133             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5134             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5135             SendToICS(buf);
5136             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5137             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5138             SendToICS(buf);
5139             i = boards[backwardMostMove][EP_STATUS];
5140             if(i >= 0) { // set e.p.
5141               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5142                 SendToICS(buf);
5143             }
5144             bsetup++;
5145           }
5146         }
5147       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5148             SendToICS("bsetup done\n"); // switch to normal examining.
5149     }
5150     for(i = backwardMostMove; i<last; i++) {
5151         char buf[20];
5152         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5153         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5154             int len = strlen(moveList[i]);
5155             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5156             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5157         }
5158         SendToICS(buf);
5159     }
5160     SendToICS(ics_prefix);
5161     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5162 }
5163
5164 void
5165 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5166 {
5167     if (rf == DROP_RANK) {
5168       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5169       sprintf(move, "%c@%c%c\n",
5170                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5171     } else {
5172         if (promoChar == 'x' || promoChar == NULLCHAR) {
5173           sprintf(move, "%c%c%c%c\n",
5174                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5175         } else {
5176             sprintf(move, "%c%c%c%c%c\n",
5177                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5178         }
5179     }
5180 }
5181
5182 void
5183 ProcessICSInitScript (FILE *f)
5184 {
5185     char buf[MSG_SIZ];
5186
5187     while (fgets(buf, MSG_SIZ, f)) {
5188         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5189     }
5190
5191     fclose(f);
5192 }
5193
5194
5195 static int lastX, lastY, selectFlag, dragging;
5196
5197 void
5198 Sweep (int step)
5199 {
5200     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5201     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5202     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5203     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5204     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5205     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5206     do {
5207         promoSweep -= step;
5208         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5209         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5210         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5211         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5212         if(!step) step = -1;
5213     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5214             appData.testLegality && (promoSweep == king ||
5215             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5216     if(toX >= 0) {
5217         int victim = boards[currentMove][toY][toX];
5218         boards[currentMove][toY][toX] = promoSweep;
5219         DrawPosition(FALSE, boards[currentMove]);
5220         boards[currentMove][toY][toX] = victim;
5221     } else
5222     ChangeDragPiece(promoSweep);
5223 }
5224
5225 int
5226 PromoScroll (int x, int y)
5227 {
5228   int step = 0;
5229
5230   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5231   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5232   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5233   if(!step) return FALSE;
5234   lastX = x; lastY = y;
5235   if((promoSweep < BlackPawn) == flipView) step = -step;
5236   if(step > 0) selectFlag = 1;
5237   if(!selectFlag) Sweep(step);
5238   return FALSE;
5239 }
5240
5241 void
5242 NextPiece (int step)
5243 {
5244     ChessSquare piece = boards[currentMove][toY][toX];
5245     do {
5246         pieceSweep -= step;
5247         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5248         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5249         if(!step) step = -1;
5250     } while(PieceToChar(pieceSweep) == '.');
5251     boards[currentMove][toY][toX] = pieceSweep;
5252     DrawPosition(FALSE, boards[currentMove]);
5253     boards[currentMove][toY][toX] = piece;
5254 }
5255 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5256 void
5257 AlphaRank (char *move, int n)
5258 {
5259 //    char *p = move, c; int x, y;
5260
5261     if (appData.debugMode) {
5262         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5263     }
5264
5265     if(move[1]=='*' &&
5266        move[2]>='0' && move[2]<='9' &&
5267        move[3]>='a' && move[3]<='x'    ) {
5268         move[1] = '@';
5269         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5270         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5271     } else
5272     if(move[0]>='0' && move[0]<='9' &&
5273        move[1]>='a' && move[1]<='x' &&
5274        move[2]>='0' && move[2]<='9' &&
5275        move[3]>='a' && move[3]<='x'    ) {
5276         /* input move, Shogi -> normal */
5277         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5278         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5279         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5280         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5281     } else
5282     if(move[1]=='@' &&
5283        move[3]>='0' && move[3]<='9' &&
5284        move[2]>='a' && move[2]<='x'    ) {
5285         move[1] = '*';
5286         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5287         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5288     } else
5289     if(
5290        move[0]>='a' && move[0]<='x' &&
5291        move[3]>='0' && move[3]<='9' &&
5292        move[2]>='a' && move[2]<='x'    ) {
5293          /* output move, normal -> Shogi */
5294         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5295         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5296         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5297         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5298         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5299     }
5300     if (appData.debugMode) {
5301         fprintf(debugFP, "   out = '%s'\n", move);
5302     }
5303 }
5304
5305 char yy_textstr[8000];
5306
5307 /* Parser for moves from gnuchess, ICS, or user typein box */
5308 Boolean
5309 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5310 {
5311     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5312
5313     switch (*moveType) {
5314       case WhitePromotion:
5315       case BlackPromotion:
5316       case WhiteNonPromotion:
5317       case BlackNonPromotion:
5318       case NormalMove:
5319       case WhiteCapturesEnPassant:
5320       case BlackCapturesEnPassant:
5321       case WhiteKingSideCastle:
5322       case WhiteQueenSideCastle:
5323       case BlackKingSideCastle:
5324       case BlackQueenSideCastle:
5325       case WhiteKingSideCastleWild:
5326       case WhiteQueenSideCastleWild:
5327       case BlackKingSideCastleWild:
5328       case BlackQueenSideCastleWild:
5329       /* Code added by Tord: */
5330       case WhiteHSideCastleFR:
5331       case WhiteASideCastleFR:
5332       case BlackHSideCastleFR:
5333       case BlackASideCastleFR:
5334       /* End of code added by Tord */
5335       case IllegalMove:         /* bug or odd chess variant */
5336         *fromX = currentMoveString[0] - AAA;
5337         *fromY = currentMoveString[1] - ONE;
5338         *toX = currentMoveString[2] - AAA;
5339         *toY = currentMoveString[3] - ONE;
5340         *promoChar = currentMoveString[4];
5341         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5342             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5343     if (appData.debugMode) {
5344         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5345     }
5346             *fromX = *fromY = *toX = *toY = 0;
5347             return FALSE;
5348         }
5349         if (appData.testLegality) {
5350           return (*moveType != IllegalMove);
5351         } else {
5352           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5353                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5354         }
5355
5356       case WhiteDrop:
5357       case BlackDrop:
5358         *fromX = *moveType == WhiteDrop ?
5359           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5360           (int) CharToPiece(ToLower(currentMoveString[0]));
5361         *fromY = DROP_RANK;
5362         *toX = currentMoveString[2] - AAA;
5363         *toY = currentMoveString[3] - ONE;
5364         *promoChar = NULLCHAR;
5365         return TRUE;
5366
5367       case AmbiguousMove:
5368       case ImpossibleMove:
5369       case EndOfFile:
5370       case ElapsedTime:
5371       case Comment:
5372       case PGNTag:
5373       case NAG:
5374       case WhiteWins:
5375       case BlackWins:
5376       case GameIsDrawn:
5377       default:
5378     if (appData.debugMode) {
5379         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5380     }
5381         /* bug? */
5382         *fromX = *fromY = *toX = *toY = 0;
5383         *promoChar = NULLCHAR;
5384         return FALSE;
5385     }
5386 }
5387
5388 Boolean pushed = FALSE;
5389 char *lastParseAttempt;
5390
5391 void
5392 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5393 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5394   int fromX, fromY, toX, toY; char promoChar;
5395   ChessMove moveType;
5396   Boolean valid;
5397   int nr = 0;
5398
5399   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5400     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5401     pushed = TRUE;
5402   }
5403   endPV = forwardMostMove;
5404   do {
5405     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5406     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5407     lastParseAttempt = pv;
5408     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5409     if(!valid && nr == 0 &&
5410        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5411         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5412         // Hande case where played move is different from leading PV move
5413         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5414         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5415         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5416         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5417           endPV += 2; // if position different, keep this
5418           moveList[endPV-1][0] = fromX + AAA;
5419           moveList[endPV-1][1] = fromY + ONE;
5420           moveList[endPV-1][2] = toX + AAA;
5421           moveList[endPV-1][3] = toY + ONE;
5422           parseList[endPV-1][0] = NULLCHAR;
5423           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5424         }
5425       }
5426     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5427     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5428     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5429     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5430         valid++; // allow comments in PV
5431         continue;
5432     }
5433     nr++;
5434     if(endPV+1 > framePtr) break; // no space, truncate
5435     if(!valid) break;
5436     endPV++;
5437     CopyBoard(boards[endPV], boards[endPV-1]);
5438     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5439     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5440     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5441     CoordsToAlgebraic(boards[endPV - 1],
5442                              PosFlags(endPV - 1),
5443                              fromY, fromX, toY, toX, promoChar,
5444                              parseList[endPV - 1]);
5445   } while(valid);
5446   if(atEnd == 2) return; // used hidden, for PV conversion
5447   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5448   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5449   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5450                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5451   DrawPosition(TRUE, boards[currentMove]);
5452 }
5453
5454 int
5455 MultiPV (ChessProgramState *cps)
5456 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5457         int i;
5458         for(i=0; i<cps->nrOptions; i++)
5459             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5460                 return i;
5461         return -1;
5462 }
5463
5464 Boolean
5465 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5466 {
5467         int startPV, multi, lineStart, origIndex = index;
5468         char *p, buf2[MSG_SIZ];
5469         ChessProgramState *cps = (pane ? &second : &first);
5470
5471         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5472         lastX = x; lastY = y;
5473         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5474         lineStart = startPV = index;
5475         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5476         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5477         index = startPV;
5478         do{ while(buf[index] && buf[index] != '\n') index++;
5479         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5480         buf[index] = 0;
5481         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5482                 int n = cps->option[multi].value;
5483                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5484                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5485                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5486                 cps->option[multi].value = n;
5487                 *start = *end = 0;
5488                 return FALSE;
5489         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5490                 ExcludeClick(origIndex - lineStart);
5491                 return FALSE;
5492         }
5493         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5494         *start = startPV; *end = index-1;
5495         return TRUE;
5496 }
5497
5498 char *
5499 PvToSAN (char *pv)
5500 {
5501         static char buf[10*MSG_SIZ];
5502         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5503         *buf = NULLCHAR;
5504         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5505         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5506         for(i = forwardMostMove; i<endPV; i++){
5507             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5508             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5509             k += strlen(buf+k);
5510         }
5511         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5512         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5513         endPV = savedEnd;
5514         return buf;
5515 }
5516
5517 Boolean
5518 LoadPV (int x, int y)
5519 { // called on right mouse click to load PV
5520   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5521   lastX = x; lastY = y;
5522   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5523   return TRUE;
5524 }
5525
5526 void
5527 UnLoadPV ()
5528 {
5529   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5530   if(endPV < 0) return;
5531   if(appData.autoCopyPV) CopyFENToClipboard();
5532   endPV = -1;
5533   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5534         Boolean saveAnimate = appData.animate;
5535         if(pushed) {
5536             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5537                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5538             } else storedGames--; // abandon shelved tail of original game
5539         }
5540         pushed = FALSE;
5541         forwardMostMove = currentMove;
5542         currentMove = oldFMM;
5543         appData.animate = FALSE;
5544         ToNrEvent(forwardMostMove);
5545         appData.animate = saveAnimate;
5546   }
5547   currentMove = forwardMostMove;
5548   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5549   ClearPremoveHighlights();
5550   DrawPosition(TRUE, boards[currentMove]);
5551 }
5552
5553 void
5554 MovePV (int x, int y, int h)
5555 { // step through PV based on mouse coordinates (called on mouse move)
5556   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5557
5558   // we must somehow check if right button is still down (might be released off board!)
5559   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5560   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5561   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5562   if(!step) return;
5563   lastX = x; lastY = y;
5564
5565   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5566   if(endPV < 0) return;
5567   if(y < margin) step = 1; else
5568   if(y > h - margin) step = -1;
5569   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5570   currentMove += step;
5571   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5572   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5573                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5574   DrawPosition(FALSE, boards[currentMove]);
5575 }
5576
5577
5578 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5579 // All positions will have equal probability, but the current method will not provide a unique
5580 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5581 #define DARK 1
5582 #define LITE 2
5583 #define ANY 3
5584
5585 int squaresLeft[4];
5586 int piecesLeft[(int)BlackPawn];
5587 int seed, nrOfShuffles;
5588
5589 void
5590 GetPositionNumber ()
5591 {       // sets global variable seed
5592         int i;
5593
5594         seed = appData.defaultFrcPosition;
5595         if(seed < 0) { // randomize based on time for negative FRC position numbers
5596                 for(i=0; i<50; i++) seed += random();
5597                 seed = random() ^ random() >> 8 ^ random() << 8;
5598                 if(seed<0) seed = -seed;
5599         }
5600 }
5601
5602 int
5603 put (Board board, int pieceType, int rank, int n, int shade)
5604 // put the piece on the (n-1)-th empty squares of the given shade
5605 {
5606         int i;
5607
5608         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5609                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5610                         board[rank][i] = (ChessSquare) pieceType;
5611                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5612                         squaresLeft[ANY]--;
5613                         piecesLeft[pieceType]--;
5614                         return i;
5615                 }
5616         }
5617         return -1;
5618 }
5619
5620
5621 void
5622 AddOnePiece (Board board, int pieceType, int rank, int shade)
5623 // calculate where the next piece goes, (any empty square), and put it there
5624 {
5625         int i;
5626
5627         i = seed % squaresLeft[shade];
5628         nrOfShuffles *= squaresLeft[shade];
5629         seed /= squaresLeft[shade];
5630         put(board, pieceType, rank, i, shade);
5631 }
5632
5633 void
5634 AddTwoPieces (Board board, int pieceType, int rank)
5635 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5636 {
5637         int i, n=squaresLeft[ANY], j=n-1, k;
5638
5639         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5640         i = seed % k;  // pick one
5641         nrOfShuffles *= k;
5642         seed /= k;
5643         while(i >= j) i -= j--;
5644         j = n - 1 - j; i += j;
5645         put(board, pieceType, rank, j, ANY);
5646         put(board, pieceType, rank, i, ANY);
5647 }
5648
5649 void
5650 SetUpShuffle (Board board, int number)
5651 {
5652         int i, p, first=1;
5653
5654         GetPositionNumber(); nrOfShuffles = 1;
5655
5656         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5657         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5658         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5659
5660         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5661
5662         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5663             p = (int) board[0][i];
5664             if(p < (int) BlackPawn) piecesLeft[p] ++;
5665             board[0][i] = EmptySquare;
5666         }
5667
5668         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5669             // shuffles restricted to allow normal castling put KRR first
5670             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5671                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5672             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5673                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5674             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5675                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5676             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5677                 put(board, WhiteRook, 0, 0, ANY);
5678             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5679         }
5680
5681         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5682             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5683             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5684                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5685                 while(piecesLeft[p] >= 2) {
5686                     AddOnePiece(board, p, 0, LITE);
5687                     AddOnePiece(board, p, 0, DARK);
5688                 }
5689                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5690             }
5691
5692         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5693             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5694             // but we leave King and Rooks for last, to possibly obey FRC restriction
5695             if(p == (int)WhiteRook) continue;
5696             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5697             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5698         }
5699
5700         // now everything is placed, except perhaps King (Unicorn) and Rooks
5701
5702         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5703             // Last King gets castling rights
5704             while(piecesLeft[(int)WhiteUnicorn]) {
5705                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5706                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5707             }
5708
5709             while(piecesLeft[(int)WhiteKing]) {
5710                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5711                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5712             }
5713
5714
5715         } else {
5716             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5717             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5718         }
5719
5720         // Only Rooks can be left; simply place them all
5721         while(piecesLeft[(int)WhiteRook]) {
5722                 i = put(board, WhiteRook, 0, 0, ANY);
5723                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5724                         if(first) {
5725                                 first=0;
5726                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5727                         }
5728                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5729                 }
5730         }
5731         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5732             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5733         }
5734
5735         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5736 }
5737
5738 int
5739 SetCharTable (char *table, const char * map)
5740 /* [HGM] moved here from winboard.c because of its general usefulness */
5741 /*       Basically a safe strcpy that uses the last character as King */
5742 {
5743     int result = FALSE; int NrPieces;
5744
5745     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5746                     && NrPieces >= 12 && !(NrPieces&1)) {
5747         int i; /* [HGM] Accept even length from 12 to 34 */
5748
5749         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5750         for( i=0; i<NrPieces/2-1; i++ ) {
5751             table[i] = map[i];
5752             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5753         }
5754         table[(int) WhiteKing]  = map[NrPieces/2-1];
5755         table[(int) BlackKing]  = map[NrPieces-1];
5756
5757         result = TRUE;
5758     }
5759
5760     return result;
5761 }
5762
5763 void
5764 Prelude (Board board)
5765 {       // [HGM] superchess: random selection of exo-pieces
5766         int i, j, k; ChessSquare p;
5767         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5768
5769         GetPositionNumber(); // use FRC position number
5770
5771         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5772             SetCharTable(pieceToChar, appData.pieceToCharTable);
5773             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5774                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5775         }
5776
5777         j = seed%4;                 seed /= 4;
5778         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5779         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5780         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5781         j = seed%3 + (seed%3 >= j); seed /= 3;
5782         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5783         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5784         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5785         j = seed%3;                 seed /= 3;
5786         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5787         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5788         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5789         j = seed%2 + (seed%2 >= j); seed /= 2;
5790         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5791         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5792         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5793         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5794         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5795         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5796         put(board, exoPieces[0],    0, 0, ANY);
5797         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5798 }
5799
5800 void
5801 InitPosition (int redraw)
5802 {
5803     ChessSquare (* pieces)[BOARD_FILES];
5804     int i, j, pawnRow, overrule,
5805     oldx = gameInfo.boardWidth,
5806     oldy = gameInfo.boardHeight,
5807     oldh = gameInfo.holdingsWidth;
5808     static int oldv;
5809
5810     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5811
5812     /* [AS] Initialize pv info list [HGM] and game status */
5813     {
5814         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5815             pvInfoList[i].depth = 0;
5816             boards[i][EP_STATUS] = EP_NONE;
5817             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5818         }
5819
5820         initialRulePlies = 0; /* 50-move counter start */
5821
5822         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5823         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5824     }
5825
5826
5827     /* [HGM] logic here is completely changed. In stead of full positions */
5828     /* the initialized data only consist of the two backranks. The switch */
5829     /* selects which one we will use, which is than copied to the Board   */
5830     /* initialPosition, which for the rest is initialized by Pawns and    */
5831     /* empty squares. This initial position is then copied to boards[0],  */
5832     /* possibly after shuffling, so that it remains available.            */
5833
5834     gameInfo.holdingsWidth = 0; /* default board sizes */
5835     gameInfo.boardWidth    = 8;
5836     gameInfo.boardHeight   = 8;
5837     gameInfo.holdingsSize  = 0;
5838     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5839     for(i=0; i<BOARD_FILES-2; i++)
5840       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5841     initialPosition[EP_STATUS] = EP_NONE;
5842     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5843     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5844          SetCharTable(pieceNickName, appData.pieceNickNames);
5845     else SetCharTable(pieceNickName, "............");
5846     pieces = FIDEArray;
5847
5848     switch (gameInfo.variant) {
5849     case VariantFischeRandom:
5850       shuffleOpenings = TRUE;
5851     default:
5852       break;
5853     case VariantShatranj:
5854       pieces = ShatranjArray;
5855       nrCastlingRights = 0;
5856       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5857       break;
5858     case VariantMakruk:
5859       pieces = makrukArray;
5860       nrCastlingRights = 0;
5861       startedFromSetupPosition = TRUE;
5862       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5863       break;
5864     case VariantTwoKings:
5865       pieces = twoKingsArray;
5866       break;
5867     case VariantGrand:
5868       pieces = GrandArray;
5869       nrCastlingRights = 0;
5870       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5871       gameInfo.boardWidth = 10;
5872       gameInfo.boardHeight = 10;
5873       gameInfo.holdingsSize = 7;
5874       break;
5875     case VariantCapaRandom:
5876       shuffleOpenings = TRUE;
5877     case VariantCapablanca:
5878       pieces = CapablancaArray;
5879       gameInfo.boardWidth = 10;
5880       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5881       break;
5882     case VariantGothic:
5883       pieces = GothicArray;
5884       gameInfo.boardWidth = 10;
5885       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5886       break;
5887     case VariantSChess:
5888       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5889       gameInfo.holdingsSize = 7;
5890       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5891       break;
5892     case VariantJanus:
5893       pieces = JanusArray;
5894       gameInfo.boardWidth = 10;
5895       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5896       nrCastlingRights = 6;
5897         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5898         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5899         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5900         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5901         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5902         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5903       break;
5904     case VariantFalcon:
5905       pieces = FalconArray;
5906       gameInfo.boardWidth = 10;
5907       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5908       break;
5909     case VariantXiangqi:
5910       pieces = XiangqiArray;
5911       gameInfo.boardWidth  = 9;
5912       gameInfo.boardHeight = 10;
5913       nrCastlingRights = 0;
5914       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5915       break;
5916     case VariantShogi:
5917       pieces = ShogiArray;
5918       gameInfo.boardWidth  = 9;
5919       gameInfo.boardHeight = 9;
5920       gameInfo.holdingsSize = 7;
5921       nrCastlingRights = 0;
5922       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5923       break;
5924     case VariantCourier:
5925       pieces = CourierArray;
5926       gameInfo.boardWidth  = 12;
5927       nrCastlingRights = 0;
5928       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5929       break;
5930     case VariantKnightmate:
5931       pieces = KnightmateArray;
5932       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5933       break;
5934     case VariantSpartan:
5935       pieces = SpartanArray;
5936       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5937       break;
5938     case VariantFairy:
5939       pieces = fairyArray;
5940       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5941       break;
5942     case VariantGreat:
5943       pieces = GreatArray;
5944       gameInfo.boardWidth = 10;
5945       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5946       gameInfo.holdingsSize = 8;
5947       break;
5948     case VariantSuper:
5949       pieces = FIDEArray;
5950       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5951       gameInfo.holdingsSize = 8;
5952       startedFromSetupPosition = TRUE;
5953       break;
5954     case VariantCrazyhouse:
5955     case VariantBughouse:
5956       pieces = FIDEArray;
5957       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5958       gameInfo.holdingsSize = 5;
5959       break;
5960     case VariantWildCastle:
5961       pieces = FIDEArray;
5962       /* !!?shuffle with kings guaranteed to be on d or e file */
5963       shuffleOpenings = 1;
5964       break;
5965     case VariantNoCastle:
5966       pieces = FIDEArray;
5967       nrCastlingRights = 0;
5968       /* !!?unconstrained back-rank shuffle */
5969       shuffleOpenings = 1;
5970       break;
5971     }
5972
5973     overrule = 0;
5974     if(appData.NrFiles >= 0) {
5975         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5976         gameInfo.boardWidth = appData.NrFiles;
5977     }
5978     if(appData.NrRanks >= 0) {
5979         gameInfo.boardHeight = appData.NrRanks;
5980     }
5981     if(appData.holdingsSize >= 0) {
5982         i = appData.holdingsSize;
5983         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5984         gameInfo.holdingsSize = i;
5985     }
5986     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5987     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5988         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5989
5990     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5991     if(pawnRow < 1) pawnRow = 1;
5992     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5993
5994     /* User pieceToChar list overrules defaults */
5995     if(appData.pieceToCharTable != NULL)
5996         SetCharTable(pieceToChar, appData.pieceToCharTable);
5997
5998     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5999
6000         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6001             s = (ChessSquare) 0; /* account holding counts in guard band */
6002         for( i=0; i<BOARD_HEIGHT; i++ )
6003             initialPosition[i][j] = s;
6004
6005         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6006         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6007         initialPosition[pawnRow][j] = WhitePawn;
6008         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6009         if(gameInfo.variant == VariantXiangqi) {
6010             if(j&1) {
6011                 initialPosition[pawnRow][j] =
6012                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6013                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6014                    initialPosition[2][j] = WhiteCannon;
6015                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6016                 }
6017             }
6018         }
6019         if(gameInfo.variant == VariantGrand) {
6020             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6021                initialPosition[0][j] = WhiteRook;
6022                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6023             }
6024         }
6025         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6026     }
6027     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6028
6029             j=BOARD_LEFT+1;
6030             initialPosition[1][j] = WhiteBishop;
6031             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6032             j=BOARD_RGHT-2;
6033             initialPosition[1][j] = WhiteRook;
6034             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6035     }
6036
6037     if( nrCastlingRights == -1) {
6038         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6039         /*       This sets default castling rights from none to normal corners   */
6040         /* Variants with other castling rights must set them themselves above    */
6041         nrCastlingRights = 6;
6042
6043         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6044         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6045         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6046         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6047         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6048         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6049      }
6050
6051      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6052      if(gameInfo.variant == VariantGreat) { // promotion commoners
6053         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6054         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6055         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6056         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6057      }
6058      if( gameInfo.variant == VariantSChess ) {
6059       initialPosition[1][0] = BlackMarshall;
6060       initialPosition[2][0] = BlackAngel;
6061       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6062       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6063       initialPosition[1][1] = initialPosition[2][1] = 
6064       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6065      }
6066   if (appData.debugMode) {
6067     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6068   }
6069     if(shuffleOpenings) {
6070         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6071         startedFromSetupPosition = TRUE;
6072     }
6073     if(startedFromPositionFile) {
6074       /* [HGM] loadPos: use PositionFile for every new game */
6075       CopyBoard(initialPosition, filePosition);
6076       for(i=0; i<nrCastlingRights; i++)
6077           initialRights[i] = filePosition[CASTLING][i];
6078       startedFromSetupPosition = TRUE;
6079     }
6080
6081     CopyBoard(boards[0], initialPosition);
6082
6083     if(oldx != gameInfo.boardWidth ||
6084        oldy != gameInfo.boardHeight ||
6085        oldv != gameInfo.variant ||
6086        oldh != gameInfo.holdingsWidth
6087                                          )
6088             InitDrawingSizes(-2 ,0);
6089
6090     oldv = gameInfo.variant;
6091     if (redraw)
6092       DrawPosition(TRUE, boards[currentMove]);
6093 }
6094
6095 void
6096 SendBoard (ChessProgramState *cps, int moveNum)
6097 {
6098     char message[MSG_SIZ];
6099
6100     if (cps->useSetboard) {
6101       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6102       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6103       SendToProgram(message, cps);
6104       free(fen);
6105
6106     } else {
6107       ChessSquare *bp;
6108       int i, j, left=0, right=BOARD_WIDTH;
6109       /* Kludge to set black to move, avoiding the troublesome and now
6110        * deprecated "black" command.
6111        */
6112       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6113         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6114
6115       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6116
6117       SendToProgram("edit\n", cps);
6118       SendToProgram("#\n", cps);
6119       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6120         bp = &boards[moveNum][i][left];
6121         for (j = left; j < right; j++, bp++) {
6122           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6123           if ((int) *bp < (int) BlackPawn) {
6124             if(j == BOARD_RGHT+1)
6125                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6126             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6127             if(message[0] == '+' || message[0] == '~') {
6128               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6129                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6130                         AAA + j, ONE + i);
6131             }
6132             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6133                 message[1] = BOARD_RGHT   - 1 - j + '1';
6134                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6135             }
6136             SendToProgram(message, cps);
6137           }
6138         }
6139       }
6140
6141       SendToProgram("c\n", cps);
6142       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6143         bp = &boards[moveNum][i][left];
6144         for (j = left; j < right; j++, bp++) {
6145           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6146           if (((int) *bp != (int) EmptySquare)
6147               && ((int) *bp >= (int) BlackPawn)) {
6148             if(j == BOARD_LEFT-2)
6149                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6150             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6151                     AAA + j, ONE + i);
6152             if(message[0] == '+' || message[0] == '~') {
6153               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6154                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6155                         AAA + j, ONE + i);
6156             }
6157             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6158                 message[1] = BOARD_RGHT   - 1 - j + '1';
6159                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6160             }
6161             SendToProgram(message, cps);
6162           }
6163         }
6164       }
6165
6166       SendToProgram(".\n", cps);
6167     }
6168     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6169 }
6170
6171 char exclusionHeader[MSG_SIZ];
6172 int exCnt, excludePtr;
6173 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6174 static Exclusion excluTab[200];
6175 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6176
6177 static void
6178 WriteMap (int s)
6179 {
6180     int j;
6181     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6182     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6183 }
6184
6185 static void
6186 ClearMap ()
6187 {
6188     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6189     excludePtr = 24; exCnt = 0;
6190     WriteMap(0);
6191 }
6192
6193 static void
6194 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6195 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6196     char buf[2*MOVE_LEN], *p;
6197     Exclusion *e = excluTab;
6198     int i;
6199     for(i=0; i<exCnt; i++)
6200         if(e[i].ff == fromX && e[i].fr == fromY &&
6201            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6202     if(i == exCnt) { // was not in exclude list; add it
6203         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6204         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6205             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6206             return; // abort
6207         }
6208         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6209         excludePtr++; e[i].mark = excludePtr++;
6210         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6211         exCnt++;
6212     }
6213     exclusionHeader[e[i].mark] = state;
6214 }
6215
6216 static int
6217 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6218 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6219     char buf[MSG_SIZ];
6220     int j, k;
6221     ChessMove moveType;
6222     if((signed char)promoChar == -1) { // kludge to indicate best move
6223         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6224             return 1; // if unparsable, abort
6225     }
6226     // update exclusion map (resolving toggle by consulting existing state)
6227     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6228     j = k%8; k >>= 3;
6229     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6230     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6231          excludeMap[k] |=   1<<j;
6232     else excludeMap[k] &= ~(1<<j);
6233     // update header
6234     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6235     // inform engine
6236     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6237     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6238     SendToBoth(buf);
6239     return (state == '+');
6240 }
6241
6242 static void
6243 ExcludeClick (int index)
6244 {
6245     int i, j;
6246     Exclusion *e = excluTab;
6247     if(index < 25) { // none, best or tail clicked
6248         if(index < 13) { // none: include all
6249             WriteMap(0); // clear map
6250             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6251             SendToBoth("include all\n"); // and inform engine
6252         } else if(index > 18) { // tail
6253             if(exclusionHeader[19] == '-') { // tail was excluded
6254                 SendToBoth("include all\n");
6255                 WriteMap(0); // clear map completely
6256                 // now re-exclude selected moves
6257                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6258                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6259             } else { // tail was included or in mixed state
6260                 SendToBoth("exclude all\n");
6261                 WriteMap(0xFF); // fill map completely
6262                 // now re-include selected moves
6263                 j = 0; // count them
6264                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6265                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6266                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6267             }
6268         } else { // best
6269             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6270         }
6271     } else {
6272         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6273             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6274             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6275             break;
6276         }
6277     }
6278 }
6279
6280 ChessSquare
6281 DefaultPromoChoice (int white)
6282 {
6283     ChessSquare result;
6284     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6285         result = WhiteFerz; // no choice
6286     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6287         result= WhiteKing; // in Suicide Q is the last thing we want
6288     else if(gameInfo.variant == VariantSpartan)
6289         result = white ? WhiteQueen : WhiteAngel;
6290     else result = WhiteQueen;
6291     if(!white) result = WHITE_TO_BLACK result;
6292     return result;
6293 }
6294
6295 static int autoQueen; // [HGM] oneclick
6296
6297 int
6298 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6299 {
6300     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6301     /* [HGM] add Shogi promotions */
6302     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6303     ChessSquare piece;
6304     ChessMove moveType;
6305     Boolean premove;
6306
6307     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6308     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6309
6310     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6311       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6312         return FALSE;
6313
6314     piece = boards[currentMove][fromY][fromX];
6315     if(gameInfo.variant == VariantShogi) {
6316         promotionZoneSize = BOARD_HEIGHT/3;
6317         highestPromotingPiece = (int)WhiteFerz;
6318     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6319         promotionZoneSize = 3;
6320     }
6321
6322     // Treat Lance as Pawn when it is not representing Amazon
6323     if(gameInfo.variant != VariantSuper) {
6324         if(piece == WhiteLance) piece = WhitePawn; else
6325         if(piece == BlackLance) piece = BlackPawn;
6326     }
6327
6328     // next weed out all moves that do not touch the promotion zone at all
6329     if((int)piece >= BlackPawn) {
6330         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6331              return FALSE;
6332         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6333     } else {
6334         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6335            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6336     }
6337
6338     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6339
6340     // weed out mandatory Shogi promotions
6341     if(gameInfo.variant == VariantShogi) {
6342         if(piece >= BlackPawn) {
6343             if(toY == 0 && piece == BlackPawn ||
6344                toY == 0 && piece == BlackQueen ||
6345                toY <= 1 && piece == BlackKnight) {
6346                 *promoChoice = '+';
6347                 return FALSE;
6348             }
6349         } else {
6350             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6351                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6352                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6353                 *promoChoice = '+';
6354                 return FALSE;
6355             }
6356         }
6357     }
6358
6359     // weed out obviously illegal Pawn moves
6360     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6361         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6362         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6363         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6364         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6365         // note we are not allowed to test for valid (non-)capture, due to premove
6366     }
6367
6368     // we either have a choice what to promote to, or (in Shogi) whether to promote
6369     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6370         *promoChoice = PieceToChar(BlackFerz);  // no choice
6371         return FALSE;
6372     }
6373     // no sense asking what we must promote to if it is going to explode...
6374     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6375         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6376         return FALSE;
6377     }
6378     // give caller the default choice even if we will not make it
6379     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6380     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6381     if(        sweepSelect && gameInfo.variant != VariantGreat
6382                            && gameInfo.variant != VariantGrand
6383                            && gameInfo.variant != VariantSuper) return FALSE;
6384     if(autoQueen) return FALSE; // predetermined
6385
6386     // suppress promotion popup on illegal moves that are not premoves
6387     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6388               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6389     if(appData.testLegality && !premove) {
6390         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6391                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6392         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6393             return FALSE;
6394     }
6395
6396     return TRUE;
6397 }
6398
6399 int
6400 InPalace (int row, int column)
6401 {   /* [HGM] for Xiangqi */
6402     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6403          column < (BOARD_WIDTH + 4)/2 &&
6404          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6405     return FALSE;
6406 }
6407
6408 int
6409 PieceForSquare (int x, int y)
6410 {
6411   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6412      return -1;
6413   else
6414      return boards[currentMove][y][x];
6415 }
6416
6417 int
6418 OKToStartUserMove (int x, int y)
6419 {
6420     ChessSquare from_piece;
6421     int white_piece;
6422
6423     if (matchMode) return FALSE;
6424     if (gameMode == EditPosition) return TRUE;
6425
6426     if (x >= 0 && y >= 0)
6427       from_piece = boards[currentMove][y][x];
6428     else
6429       from_piece = EmptySquare;
6430
6431     if (from_piece == EmptySquare) return FALSE;
6432
6433     white_piece = (int)from_piece >= (int)WhitePawn &&
6434       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6435
6436     switch (gameMode) {
6437       case AnalyzeFile:
6438       case TwoMachinesPlay:
6439       case EndOfGame:
6440         return FALSE;
6441
6442       case IcsObserving:
6443       case IcsIdle:
6444         return FALSE;
6445
6446       case MachinePlaysWhite:
6447       case IcsPlayingBlack:
6448         if (appData.zippyPlay) return FALSE;
6449         if (white_piece) {
6450             DisplayMoveError(_("You are playing Black"));
6451             return FALSE;
6452         }
6453         break;
6454
6455       case MachinePlaysBlack:
6456       case IcsPlayingWhite:
6457         if (appData.zippyPlay) return FALSE;
6458         if (!white_piece) {
6459             DisplayMoveError(_("You are playing White"));
6460             return FALSE;
6461         }
6462         break;
6463
6464       case PlayFromGameFile:
6465             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6466       case EditGame:
6467         if (!white_piece && WhiteOnMove(currentMove)) {
6468             DisplayMoveError(_("It is White's turn"));
6469             return FALSE;
6470         }
6471         if (white_piece && !WhiteOnMove(currentMove)) {
6472             DisplayMoveError(_("It is Black's turn"));
6473             return FALSE;
6474         }
6475         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6476             /* Editing correspondence game history */
6477             /* Could disallow this or prompt for confirmation */
6478             cmailOldMove = -1;
6479         }
6480         break;
6481
6482       case BeginningOfGame:
6483         if (appData.icsActive) return FALSE;
6484         if (!appData.noChessProgram) {
6485             if (!white_piece) {
6486                 DisplayMoveError(_("You are playing White"));
6487                 return FALSE;
6488             }
6489         }
6490         break;
6491
6492       case Training:
6493         if (!white_piece && WhiteOnMove(currentMove)) {
6494             DisplayMoveError(_("It is White's turn"));
6495             return FALSE;
6496         }
6497         if (white_piece && !WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is Black's turn"));
6499             return FALSE;
6500         }
6501         break;
6502
6503       default:
6504       case IcsExamining:
6505         break;
6506     }
6507     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6508         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6509         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6510         && gameMode != AnalyzeFile && gameMode != Training) {
6511         DisplayMoveError(_("Displayed position is not current"));
6512         return FALSE;
6513     }
6514     return TRUE;
6515 }
6516
6517 Boolean
6518 OnlyMove (int *x, int *y, Boolean captures) 
6519 {
6520     DisambiguateClosure cl;
6521     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6522     switch(gameMode) {
6523       case MachinePlaysBlack:
6524       case IcsPlayingWhite:
6525       case BeginningOfGame:
6526         if(!WhiteOnMove(currentMove)) return FALSE;
6527         break;
6528       case MachinePlaysWhite:
6529       case IcsPlayingBlack:
6530         if(WhiteOnMove(currentMove)) return FALSE;
6531         break;
6532       case EditGame:
6533         break;
6534       default:
6535         return FALSE;
6536     }
6537     cl.pieceIn = EmptySquare;
6538     cl.rfIn = *y;
6539     cl.ffIn = *x;
6540     cl.rtIn = -1;
6541     cl.ftIn = -1;
6542     cl.promoCharIn = NULLCHAR;
6543     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6544     if( cl.kind == NormalMove ||
6545         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6546         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6547         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6548       fromX = cl.ff;
6549       fromY = cl.rf;
6550       *x = cl.ft;
6551       *y = cl.rt;
6552       return TRUE;
6553     }
6554     if(cl.kind != ImpossibleMove) return FALSE;
6555     cl.pieceIn = EmptySquare;
6556     cl.rfIn = -1;
6557     cl.ffIn = -1;
6558     cl.rtIn = *y;
6559     cl.ftIn = *x;
6560     cl.promoCharIn = NULLCHAR;
6561     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6562     if( cl.kind == NormalMove ||
6563         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6564         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6565         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6566       fromX = cl.ff;
6567       fromY = cl.rf;
6568       *x = cl.ft;
6569       *y = cl.rt;
6570       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6571       return TRUE;
6572     }
6573     return FALSE;
6574 }
6575
6576 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6577 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6578 int lastLoadGameUseList = FALSE;
6579 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6580 ChessMove lastLoadGameStart = EndOfFile;
6581 int doubleClick;
6582
6583 void
6584 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6585 {
6586     ChessMove moveType;
6587     ChessSquare pup;
6588     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6589
6590     /* Check if the user is playing in turn.  This is complicated because we
6591        let the user "pick up" a piece before it is his turn.  So the piece he
6592        tried to pick up may have been captured by the time he puts it down!
6593        Therefore we use the color the user is supposed to be playing in this
6594        test, not the color of the piece that is currently on the starting
6595        square---except in EditGame mode, where the user is playing both
6596        sides; fortunately there the capture race can't happen.  (It can
6597        now happen in IcsExamining mode, but that's just too bad.  The user
6598        will get a somewhat confusing message in that case.)
6599        */
6600
6601     switch (gameMode) {
6602       case AnalyzeFile:
6603       case TwoMachinesPlay:
6604       case EndOfGame:
6605       case IcsObserving:
6606       case IcsIdle:
6607         /* We switched into a game mode where moves are not accepted,
6608            perhaps while the mouse button was down. */
6609         return;
6610
6611       case MachinePlaysWhite:
6612         /* User is moving for Black */
6613         if (WhiteOnMove(currentMove)) {
6614             DisplayMoveError(_("It is White's turn"));
6615             return;
6616         }
6617         break;
6618
6619       case MachinePlaysBlack:
6620         /* User is moving for White */
6621         if (!WhiteOnMove(currentMove)) {
6622             DisplayMoveError(_("It is Black's turn"));
6623             return;
6624         }
6625         break;
6626
6627       case PlayFromGameFile:
6628             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6629       case EditGame:
6630       case IcsExamining:
6631       case BeginningOfGame:
6632       case AnalyzeMode:
6633       case Training:
6634         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6635         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6636             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6637             /* User is moving for Black */
6638             if (WhiteOnMove(currentMove)) {
6639                 DisplayMoveError(_("It is White's turn"));
6640                 return;
6641             }
6642         } else {
6643             /* User is moving for White */
6644             if (!WhiteOnMove(currentMove)) {
6645                 DisplayMoveError(_("It is Black's turn"));
6646                 return;
6647             }
6648         }
6649         break;
6650
6651       case IcsPlayingBlack:
6652         /* User is moving for Black */
6653         if (WhiteOnMove(currentMove)) {
6654             if (!appData.premove) {
6655                 DisplayMoveError(_("It is White's turn"));
6656             } else if (toX >= 0 && toY >= 0) {
6657                 premoveToX = toX;
6658                 premoveToY = toY;
6659                 premoveFromX = fromX;
6660                 premoveFromY = fromY;
6661                 premovePromoChar = promoChar;
6662                 gotPremove = 1;
6663                 if (appData.debugMode)
6664                     fprintf(debugFP, "Got premove: fromX %d,"
6665                             "fromY %d, toX %d, toY %d\n",
6666                             fromX, fromY, toX, toY);
6667             }
6668             return;
6669         }
6670         break;
6671
6672       case IcsPlayingWhite:
6673         /* User is moving for White */
6674         if (!WhiteOnMove(currentMove)) {
6675             if (!appData.premove) {
6676                 DisplayMoveError(_("It is Black's turn"));
6677             } else if (toX >= 0 && toY >= 0) {
6678                 premoveToX = toX;
6679                 premoveToY = toY;
6680                 premoveFromX = fromX;
6681                 premoveFromY = fromY;
6682                 premovePromoChar = promoChar;
6683                 gotPremove = 1;
6684                 if (appData.debugMode)
6685                     fprintf(debugFP, "Got premove: fromX %d,"
6686                             "fromY %d, toX %d, toY %d\n",
6687                             fromX, fromY, toX, toY);
6688             }
6689             return;
6690         }
6691         break;
6692
6693       default:
6694         break;
6695
6696       case EditPosition:
6697         /* EditPosition, empty square, or different color piece;
6698            click-click move is possible */
6699         if (toX == -2 || toY == -2) {
6700             boards[0][fromY][fromX] = EmptySquare;
6701             DrawPosition(FALSE, boards[currentMove]);
6702             return;
6703         } else if (toX >= 0 && toY >= 0) {
6704             boards[0][toY][toX] = boards[0][fromY][fromX];
6705             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6706                 if(boards[0][fromY][0] != EmptySquare) {
6707                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6708                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6709                 }
6710             } else
6711             if(fromX == BOARD_RGHT+1) {
6712                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6713                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6714                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6715                 }
6716             } else
6717             boards[0][fromY][fromX] = gatingPiece;
6718             DrawPosition(FALSE, boards[currentMove]);
6719             return;
6720         }
6721         return;
6722     }
6723
6724     if(toX < 0 || toY < 0) return;
6725     pup = boards[currentMove][toY][toX];
6726
6727     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6728     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6729          if( pup != EmptySquare ) return;
6730          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6731            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6732                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6733            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6734            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6735            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6736            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6737          fromY = DROP_RANK;
6738     }
6739
6740     /* [HGM] always test for legality, to get promotion info */
6741     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6742                                          fromY, fromX, toY, toX, promoChar);
6743
6744     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6745
6746     /* [HGM] but possibly ignore an IllegalMove result */
6747     if (appData.testLegality) {
6748         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6749             DisplayMoveError(_("Illegal move"));
6750             return;
6751         }
6752     }
6753
6754     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6755         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6756              ClearPremoveHighlights(); // was included
6757         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6758         return;
6759     }
6760
6761     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6762 }
6763
6764 /* Common tail of UserMoveEvent and DropMenuEvent */
6765 int
6766 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6767 {
6768     char *bookHit = 0;
6769
6770     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6771         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6772         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6773         if(WhiteOnMove(currentMove)) {
6774             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6775         } else {
6776             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6777         }
6778     }
6779
6780     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6781        move type in caller when we know the move is a legal promotion */
6782     if(moveType == NormalMove && promoChar)
6783         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6784
6785     /* [HGM] <popupFix> The following if has been moved here from
6786        UserMoveEvent(). Because it seemed to belong here (why not allow
6787        piece drops in training games?), and because it can only be
6788        performed after it is known to what we promote. */
6789     if (gameMode == Training) {
6790       /* compare the move played on the board to the next move in the
6791        * game. If they match, display the move and the opponent's response.
6792        * If they don't match, display an error message.
6793        */
6794       int saveAnimate;
6795       Board testBoard;
6796       CopyBoard(testBoard, boards[currentMove]);
6797       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6798
6799       if (CompareBoards(testBoard, boards[currentMove+1])) {
6800         ForwardInner(currentMove+1);
6801
6802         /* Autoplay the opponent's response.
6803          * if appData.animate was TRUE when Training mode was entered,
6804          * the response will be animated.
6805          */
6806         saveAnimate = appData.animate;
6807         appData.animate = animateTraining;
6808         ForwardInner(currentMove+1);
6809         appData.animate = saveAnimate;
6810
6811         /* check for the end of the game */
6812         if (currentMove >= forwardMostMove) {
6813           gameMode = PlayFromGameFile;
6814           ModeHighlight();
6815           SetTrainingModeOff();
6816           DisplayInformation(_("End of game"));
6817         }
6818       } else {
6819         DisplayError(_("Incorrect move"), 0);
6820       }
6821       return 1;
6822     }
6823
6824   /* Ok, now we know that the move is good, so we can kill
6825      the previous line in Analysis Mode */
6826   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6827                                 && currentMove < forwardMostMove) {
6828     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6829     else forwardMostMove = currentMove;
6830   }
6831
6832   ClearMap();
6833
6834   /* If we need the chess program but it's dead, restart it */
6835   ResurrectChessProgram();
6836
6837   /* A user move restarts a paused game*/
6838   if (pausing)
6839     PauseEvent();
6840
6841   thinkOutput[0] = NULLCHAR;
6842
6843   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6844
6845   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6846     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6847     return 1;
6848   }
6849
6850   if (gameMode == BeginningOfGame) {
6851     if (appData.noChessProgram) {
6852       gameMode = EditGame;
6853       SetGameInfo();
6854     } else {
6855       char buf[MSG_SIZ];
6856       gameMode = MachinePlaysBlack;
6857       StartClocks();
6858       SetGameInfo();
6859       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6860       DisplayTitle(buf);
6861       if (first.sendName) {
6862         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6863         SendToProgram(buf, &first);
6864       }
6865       StartClocks();
6866     }
6867     ModeHighlight();
6868   }
6869
6870   /* Relay move to ICS or chess engine */
6871   if (appData.icsActive) {
6872     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6873         gameMode == IcsExamining) {
6874       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6875         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6876         SendToICS("draw ");
6877         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6878       }
6879       // also send plain move, in case ICS does not understand atomic claims
6880       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6881       ics_user_moved = 1;
6882     }
6883   } else {
6884     if (first.sendTime && (gameMode == BeginningOfGame ||
6885                            gameMode == MachinePlaysWhite ||
6886                            gameMode == MachinePlaysBlack)) {
6887       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6888     }
6889     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6890          // [HGM] book: if program might be playing, let it use book
6891         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6892         first.maybeThinking = TRUE;
6893     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6894         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6895         SendBoard(&first, currentMove+1);
6896         if(second.analyzing) {
6897             if(!second.useSetboard) SendToProgram("undo\n", &second);
6898             SendBoard(&second, currentMove+1);
6899         }
6900     } else {
6901         SendMoveToProgram(forwardMostMove-1, &first);
6902         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6903     }
6904     if (currentMove == cmailOldMove + 1) {
6905       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6906     }
6907   }
6908
6909   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6910
6911   switch (gameMode) {
6912   case EditGame:
6913     if(appData.testLegality)
6914     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6915     case MT_NONE:
6916     case MT_CHECK:
6917       break;
6918     case MT_CHECKMATE:
6919     case MT_STAINMATE:
6920       if (WhiteOnMove(currentMove)) {
6921         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6922       } else {
6923         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6924       }
6925       break;
6926     case MT_STALEMATE:
6927       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6928       break;
6929     }
6930     break;
6931
6932   case MachinePlaysBlack:
6933   case MachinePlaysWhite:
6934     /* disable certain menu options while machine is thinking */
6935     SetMachineThinkingEnables();
6936     break;
6937
6938   default:
6939     break;
6940   }
6941
6942   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6943   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6944
6945   if(bookHit) { // [HGM] book: simulate book reply
6946         static char bookMove[MSG_SIZ]; // a bit generous?
6947
6948         programStats.nodes = programStats.depth = programStats.time =
6949         programStats.score = programStats.got_only_move = 0;
6950         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6951
6952         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6953         strcat(bookMove, bookHit);
6954         HandleMachineMove(bookMove, &first);
6955   }
6956   return 1;
6957 }
6958
6959 void
6960 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6961 {
6962     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6963     Markers *m = (Markers *) closure;
6964     if(rf == fromY && ff == fromX)
6965         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6966                          || kind == WhiteCapturesEnPassant
6967                          || kind == BlackCapturesEnPassant);
6968     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6969 }
6970
6971 void
6972 MarkTargetSquares (int clear)
6973 {
6974   int x, y;
6975   if(clear) // no reason to ever suppress clearing
6976     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6977   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6978      !appData.testLegality || gameMode == EditPosition) return;
6979   if(!clear) {
6980     int capt = 0;
6981     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6982     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6983       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6984       if(capt)
6985       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6986     }
6987   }
6988   DrawPosition(FALSE, NULL);
6989 }
6990
6991 int
6992 Explode (Board board, int fromX, int fromY, int toX, int toY)
6993 {
6994     if(gameInfo.variant == VariantAtomic &&
6995        (board[toY][toX] != EmptySquare ||                     // capture?
6996         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6997                          board[fromY][fromX] == BlackPawn   )
6998       )) {
6999         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7000         return TRUE;
7001     }
7002     return FALSE;
7003 }
7004
7005 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7006
7007 int
7008 CanPromote (ChessSquare piece, int y)
7009 {
7010         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7011         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7012         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7013            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7014            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7015                                                   gameInfo.variant == VariantMakruk) return FALSE;
7016         return (piece == BlackPawn && y == 1 ||
7017                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7018                 piece == BlackLance && y == 1 ||
7019                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7020 }
7021
7022 void
7023 LeftClick (ClickType clickType, int xPix, int yPix)
7024 {
7025     int x, y;
7026     Boolean saveAnimate;
7027     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7028     char promoChoice = NULLCHAR;
7029     ChessSquare piece;
7030     static TimeMark lastClickTime, prevClickTime;
7031
7032     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7033
7034     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7035
7036     if (clickType == Press) ErrorPopDown();
7037
7038     x = EventToSquare(xPix, BOARD_WIDTH);
7039     y = EventToSquare(yPix, BOARD_HEIGHT);
7040     if (!flipView && y >= 0) {
7041         y = BOARD_HEIGHT - 1 - y;
7042     }
7043     if (flipView && x >= 0) {
7044         x = BOARD_WIDTH - 1 - x;
7045     }
7046
7047     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7048         defaultPromoChoice = promoSweep;
7049         promoSweep = EmptySquare;   // terminate sweep
7050         promoDefaultAltered = TRUE;
7051         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7052     }
7053
7054     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7055         if(clickType == Release) return; // ignore upclick of click-click destination
7056         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7057         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7058         if(gameInfo.holdingsWidth &&
7059                 (WhiteOnMove(currentMove)
7060                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7061                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7062             // click in right holdings, for determining promotion piece
7063             ChessSquare p = boards[currentMove][y][x];
7064             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7065             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7066             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7067                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7068                 fromX = fromY = -1;
7069                 return;
7070             }
7071         }
7072         DrawPosition(FALSE, boards[currentMove]);
7073         return;
7074     }
7075
7076     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7077     if(clickType == Press
7078             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7079               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7080               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7081         return;
7082
7083     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7084         // could be static click on premove from-square: abort premove
7085         gotPremove = 0;
7086         ClearPremoveHighlights();
7087     }
7088
7089     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7090         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7091
7092     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7093         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7094                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7095         defaultPromoChoice = DefaultPromoChoice(side);
7096     }
7097
7098     autoQueen = appData.alwaysPromoteToQueen;
7099
7100     if (fromX == -1) {
7101       int originalY = y;
7102       gatingPiece = EmptySquare;
7103       if (clickType != Press) {
7104         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7105             DragPieceEnd(xPix, yPix); dragging = 0;
7106             DrawPosition(FALSE, NULL);
7107         }
7108         return;
7109       }
7110       doubleClick = FALSE;
7111       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7112         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7113       }
7114       fromX = x; fromY = y; toX = toY = -1;
7115       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7116          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7117          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7118             /* First square */
7119             if (OKToStartUserMove(fromX, fromY)) {
7120                 second = 0;
7121                 MarkTargetSquares(0);
7122                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7123                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7124                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7125                     promoSweep = defaultPromoChoice;
7126                     selectFlag = 0; lastX = xPix; lastY = yPix;
7127                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7128                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7129                 }
7130                 if (appData.highlightDragging) {
7131                     SetHighlights(fromX, fromY, -1, -1);
7132                 } else {
7133                     ClearHighlights();
7134                 }
7135             } else fromX = fromY = -1;
7136             return;
7137         }
7138     }
7139
7140     /* fromX != -1 */
7141     if (clickType == Press && gameMode != EditPosition) {
7142         ChessSquare fromP;
7143         ChessSquare toP;
7144         int frc;
7145
7146         // ignore off-board to clicks
7147         if(y < 0 || x < 0) return;
7148
7149         /* Check if clicking again on the same color piece */
7150         fromP = boards[currentMove][fromY][fromX];
7151         toP = boards[currentMove][y][x];
7152         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7153         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7154              WhitePawn <= toP && toP <= WhiteKing &&
7155              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7156              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7157             (BlackPawn <= fromP && fromP <= BlackKing &&
7158              BlackPawn <= toP && toP <= BlackKing &&
7159              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7160              !(fromP == BlackKing && toP == BlackRook && frc))) {
7161             /* Clicked again on same color piece -- changed his mind */
7162             second = (x == fromX && y == fromY);
7163             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7164                 second = FALSE; // first double-click rather than scond click
7165                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7166             }
7167             promoDefaultAltered = FALSE;
7168             MarkTargetSquares(1);
7169            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7170             if (appData.highlightDragging) {
7171                 SetHighlights(x, y, -1, -1);
7172             } else {
7173                 ClearHighlights();
7174             }
7175             if (OKToStartUserMove(x, y)) {
7176                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7177                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7178                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7179                  gatingPiece = boards[currentMove][fromY][fromX];
7180                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7181                 fromX = x;
7182                 fromY = y; dragging = 1;
7183                 MarkTargetSquares(0);
7184                 DragPieceBegin(xPix, yPix, FALSE);
7185                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7186                     promoSweep = defaultPromoChoice;
7187                     selectFlag = 0; lastX = xPix; lastY = yPix;
7188                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7189                 }
7190             }
7191            }
7192            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7193            second = FALSE; 
7194         }
7195         // ignore clicks on holdings
7196         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7197     }
7198
7199     if (clickType == Release && x == fromX && y == fromY) {
7200         DragPieceEnd(xPix, yPix); dragging = 0;
7201         if(clearFlag) {
7202             // a deferred attempt to click-click move an empty square on top of a piece
7203             boards[currentMove][y][x] = EmptySquare;
7204             ClearHighlights();
7205             DrawPosition(FALSE, boards[currentMove]);
7206             fromX = fromY = -1; clearFlag = 0;
7207             return;
7208         }
7209         if (appData.animateDragging) {
7210             /* Undo animation damage if any */
7211             DrawPosition(FALSE, NULL);
7212         }
7213         if (second || sweepSelecting) {
7214             /* Second up/down in same square; just abort move */
7215             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7216             second = sweepSelecting = 0;
7217             fromX = fromY = -1;
7218             gatingPiece = EmptySquare;
7219             ClearHighlights();
7220             gotPremove = 0;
7221             ClearPremoveHighlights();
7222         } else {
7223             /* First upclick in same square; start click-click mode */
7224             SetHighlights(x, y, -1, -1);
7225         }
7226         return;
7227     }
7228
7229     clearFlag = 0;
7230
7231     /* we now have a different from- and (possibly off-board) to-square */
7232     /* Completed move */
7233     if(!sweepSelecting) {
7234         toX = x;
7235         toY = y;
7236     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7237
7238     saveAnimate = appData.animate;
7239     if (clickType == Press) {
7240         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7241             // must be Edit Position mode with empty-square selected
7242             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7243             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7244             return;
7245         }
7246         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7247           if(appData.sweepSelect) {
7248             ChessSquare piece = boards[currentMove][fromY][fromX];
7249             promoSweep = defaultPromoChoice;
7250             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7251             selectFlag = 0; lastX = xPix; lastY = yPix;
7252             Sweep(0); // Pawn that is going to promote: preview promotion piece
7253             sweepSelecting = 1;
7254             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7255             MarkTargetSquares(1);
7256           }
7257           return; // promo popup appears on up-click
7258         }
7259         /* Finish clickclick move */
7260         if (appData.animate || appData.highlightLastMove) {
7261             SetHighlights(fromX, fromY, toX, toY);
7262         } else {
7263             ClearHighlights();
7264         }
7265     } else {
7266 #if 0
7267 // [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
7268         /* Finish drag move */
7269         if (appData.highlightLastMove) {
7270             SetHighlights(fromX, fromY, toX, toY);
7271         } else {
7272             ClearHighlights();
7273         }
7274 #endif
7275         DragPieceEnd(xPix, yPix); dragging = 0;
7276         /* Don't animate move and drag both */
7277         appData.animate = FALSE;
7278     }
7279
7280     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7281     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7282         ChessSquare piece = boards[currentMove][fromY][fromX];
7283         if(gameMode == EditPosition && piece != EmptySquare &&
7284            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7285             int n;
7286
7287             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7288                 n = PieceToNumber(piece - (int)BlackPawn);
7289                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7290                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7291                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7292             } else
7293             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7294                 n = PieceToNumber(piece);
7295                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7296                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7297                 boards[currentMove][n][BOARD_WIDTH-2]++;
7298             }
7299             boards[currentMove][fromY][fromX] = EmptySquare;
7300         }
7301         ClearHighlights();
7302         fromX = fromY = -1;
7303         MarkTargetSquares(1);
7304         DrawPosition(TRUE, boards[currentMove]);
7305         return;
7306     }
7307
7308     // off-board moves should not be highlighted
7309     if(x < 0 || y < 0) ClearHighlights();
7310
7311     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7312
7313     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7314         SetHighlights(fromX, fromY, toX, toY);
7315         MarkTargetSquares(1);
7316         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7317             // [HGM] super: promotion to captured piece selected from holdings
7318             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7319             promotionChoice = TRUE;
7320             // kludge follows to temporarily execute move on display, without promoting yet
7321             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7322             boards[currentMove][toY][toX] = p;
7323             DrawPosition(FALSE, boards[currentMove]);
7324             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7325             boards[currentMove][toY][toX] = q;
7326             DisplayMessage("Click in holdings to choose piece", "");
7327             return;
7328         }
7329         PromotionPopUp();
7330     } else {
7331         int oldMove = currentMove;
7332         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7333         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7334         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7335         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7336            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7337             DrawPosition(TRUE, boards[currentMove]);
7338         MarkTargetSquares(1);
7339         fromX = fromY = -1;
7340     }
7341     appData.animate = saveAnimate;
7342     if (appData.animate || appData.animateDragging) {
7343         /* Undo animation damage if needed */
7344         DrawPosition(FALSE, NULL);
7345     }
7346 }
7347
7348 int
7349 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7350 {   // front-end-free part taken out of PieceMenuPopup
7351     int whichMenu; int xSqr, ySqr;
7352
7353     if(seekGraphUp) { // [HGM] seekgraph
7354         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7355         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7356         return -2;
7357     }
7358
7359     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7360          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7361         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7362         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7363         if(action == Press)   {
7364             originalFlip = flipView;
7365             flipView = !flipView; // temporarily flip board to see game from partners perspective
7366             DrawPosition(TRUE, partnerBoard);
7367             DisplayMessage(partnerStatus, "");
7368             partnerUp = TRUE;
7369         } else if(action == Release) {
7370             flipView = originalFlip;
7371             DrawPosition(TRUE, boards[currentMove]);
7372             partnerUp = FALSE;
7373         }
7374         return -2;
7375     }
7376
7377     xSqr = EventToSquare(x, BOARD_WIDTH);
7378     ySqr = EventToSquare(y, BOARD_HEIGHT);
7379     if (action == Release) {
7380         if(pieceSweep != EmptySquare) {
7381             EditPositionMenuEvent(pieceSweep, toX, toY);
7382             pieceSweep = EmptySquare;
7383         } else UnLoadPV(); // [HGM] pv
7384     }
7385     if (action != Press) return -2; // return code to be ignored
7386     switch (gameMode) {
7387       case IcsExamining:
7388         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7389       case EditPosition:
7390         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7391         if (xSqr < 0 || ySqr < 0) return -1;
7392         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7393         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7394         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7395         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7396         NextPiece(0);
7397         return 2; // grab
7398       case IcsObserving:
7399         if(!appData.icsEngineAnalyze) return -1;
7400       case IcsPlayingWhite:
7401       case IcsPlayingBlack:
7402         if(!appData.zippyPlay) goto noZip;
7403       case AnalyzeMode:
7404       case AnalyzeFile:
7405       case MachinePlaysWhite:
7406       case MachinePlaysBlack:
7407       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7408         if (!appData.dropMenu) {
7409           LoadPV(x, y);
7410           return 2; // flag front-end to grab mouse events
7411         }
7412         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7413            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7414       case EditGame:
7415       noZip:
7416         if (xSqr < 0 || ySqr < 0) return -1;
7417         if (!appData.dropMenu || appData.testLegality &&
7418             gameInfo.variant != VariantBughouse &&
7419             gameInfo.variant != VariantCrazyhouse) return -1;
7420         whichMenu = 1; // drop menu
7421         break;
7422       default:
7423         return -1;
7424     }
7425
7426     if (((*fromX = xSqr) < 0) ||
7427         ((*fromY = ySqr) < 0)) {
7428         *fromX = *fromY = -1;
7429         return -1;
7430     }
7431     if (flipView)
7432       *fromX = BOARD_WIDTH - 1 - *fromX;
7433     else
7434       *fromY = BOARD_HEIGHT - 1 - *fromY;
7435
7436     return whichMenu;
7437 }
7438
7439 void
7440 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7441 {
7442 //    char * hint = lastHint;
7443     FrontEndProgramStats stats;
7444
7445     stats.which = cps == &first ? 0 : 1;
7446     stats.depth = cpstats->depth;
7447     stats.nodes = cpstats->nodes;
7448     stats.score = cpstats->score;
7449     stats.time = cpstats->time;
7450     stats.pv = cpstats->movelist;
7451     stats.hint = lastHint;
7452     stats.an_move_index = 0;
7453     stats.an_move_count = 0;
7454
7455     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7456         stats.hint = cpstats->move_name;
7457         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7458         stats.an_move_count = cpstats->nr_moves;
7459     }
7460
7461     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
7462
7463     SetProgramStats( &stats );
7464 }
7465
7466 void
7467 ClearEngineOutputPane (int which)
7468 {
7469     static FrontEndProgramStats dummyStats;
7470     dummyStats.which = which;
7471     dummyStats.pv = "#";
7472     SetProgramStats( &dummyStats );
7473 }
7474
7475 #define MAXPLAYERS 500
7476
7477 char *
7478 TourneyStandings (int display)
7479 {
7480     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7481     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7482     char result, *p, *names[MAXPLAYERS];
7483
7484     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7485         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7486     names[0] = p = strdup(appData.participants);
7487     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7488
7489     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7490
7491     while(result = appData.results[nr]) {
7492         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7493         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7494         wScore = bScore = 0;
7495         switch(result) {
7496           case '+': wScore = 2; break;
7497           case '-': bScore = 2; break;
7498           case '=': wScore = bScore = 1; break;
7499           case ' ':
7500           case '*': return strdup("busy"); // tourney not finished
7501         }
7502         score[w] += wScore;
7503         score[b] += bScore;
7504         games[w]++;
7505         games[b]++;
7506         nr++;
7507     }
7508     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7509     for(w=0; w<nPlayers; w++) {
7510         bScore = -1;
7511         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7512         ranking[w] = b; points[w] = bScore; score[b] = -2;
7513     }
7514     p = malloc(nPlayers*34+1);
7515     for(w=0; w<nPlayers && w<display; w++)
7516         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7517     free(names[0]);
7518     return p;
7519 }
7520
7521 void
7522 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7523 {       // count all piece types
7524         int p, f, r;
7525         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7526         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7527         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7528                 p = board[r][f];
7529                 pCnt[p]++;
7530                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7531                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7532                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7533                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7534                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7535                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7536         }
7537 }
7538
7539 int
7540 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7541 {
7542         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7543         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7544
7545         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7546         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7547         if(myPawns == 2 && nMine == 3) // KPP
7548             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7549         if(myPawns == 1 && nMine == 2) // KP
7550             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7551         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7552             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7553         if(myPawns) return FALSE;
7554         if(pCnt[WhiteRook+side])
7555             return pCnt[BlackRook-side] ||
7556                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7557                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7558                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7559         if(pCnt[WhiteCannon+side]) {
7560             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7561             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7562         }
7563         if(pCnt[WhiteKnight+side])
7564             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7565         return FALSE;
7566 }
7567
7568 int
7569 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7570 {
7571         VariantClass v = gameInfo.variant;
7572
7573         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7574         if(v == VariantShatranj) return TRUE; // always winnable through baring
7575         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7576         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7577
7578         if(v == VariantXiangqi) {
7579                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7580
7581                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7582                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7583                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7584                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7585                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7586                 if(stale) // we have at least one last-rank P plus perhaps C
7587                     return majors // KPKX
7588                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7589                 else // KCA*E*
7590                     return pCnt[WhiteFerz+side] // KCAK
7591                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7592                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7593                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7594
7595         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7596                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7597
7598                 if(nMine == 1) return FALSE; // bare King
7599                 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
7600                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7601                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7602                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7603                 if(pCnt[WhiteKnight+side])
7604                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7605                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7606                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7607                 if(nBishops)
7608                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7609                 if(pCnt[WhiteAlfil+side])
7610                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7611                 if(pCnt[WhiteWazir+side])
7612                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7613         }
7614
7615         return TRUE;
7616 }
7617
7618 int
7619 CompareWithRights (Board b1, Board b2)
7620 {
7621     int rights = 0;
7622     if(!CompareBoards(b1, b2)) return FALSE;
7623     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7624     /* compare castling rights */
7625     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7626            rights++; /* King lost rights, while rook still had them */
7627     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7628         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7629            rights++; /* but at least one rook lost them */
7630     }
7631     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7632            rights++;
7633     if( b1[CASTLING][5] != NoRights ) {
7634         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7635            rights++;
7636     }
7637     return rights == 0;
7638 }
7639
7640 int
7641 Adjudicate (ChessProgramState *cps)
7642 {       // [HGM] some adjudications useful with buggy engines
7643         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7644         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7645         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7646         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7647         int k, count = 0; static int bare = 1;
7648         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7649         Boolean canAdjudicate = !appData.icsActive;
7650
7651         // most tests only when we understand the game, i.e. legality-checking on
7652             if( appData.testLegality )
7653             {   /* [HGM] Some more adjudications for obstinate engines */
7654                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7655                 static int moveCount = 6;
7656                 ChessMove result;
7657                 char *reason = NULL;
7658
7659                 /* Count what is on board. */
7660                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7661
7662                 /* Some material-based adjudications that have to be made before stalemate test */
7663                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7664                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7665                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7666                      if(canAdjudicate && appData.checkMates) {
7667                          if(engineOpponent)
7668                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7669                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7670                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7671                          return 1;
7672                      }
7673                 }
7674
7675                 /* Bare King in Shatranj (loses) or Losers (wins) */
7676                 if( nrW == 1 || nrB == 1) {
7677                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7678                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7679                      if(canAdjudicate && appData.checkMates) {
7680                          if(engineOpponent)
7681                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7682                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7683                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7684                          return 1;
7685                      }
7686                   } else
7687                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7688                   {    /* bare King */
7689                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7690                         if(canAdjudicate && appData.checkMates) {
7691                             /* but only adjudicate if adjudication enabled */
7692                             if(engineOpponent)
7693                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7694                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7695                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7696                             return 1;
7697                         }
7698                   }
7699                 } else bare = 1;
7700
7701
7702             // don't wait for engine to announce game end if we can judge ourselves
7703             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7704               case MT_CHECK:
7705                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7706                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7707                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7708                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7709                             checkCnt++;
7710                         if(checkCnt >= 2) {
7711                             reason = "Xboard adjudication: 3rd check";
7712                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7713                             break;
7714                         }
7715                     }
7716                 }
7717               case MT_NONE:
7718               default:
7719                 break;
7720               case MT_STALEMATE:
7721               case MT_STAINMATE:
7722                 reason = "Xboard adjudication: Stalemate";
7723                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7724                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7725                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7726                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7727                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7728                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7729                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7730                                                                         EP_CHECKMATE : EP_WINS);
7731                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7732                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7733                 }
7734                 break;
7735               case MT_CHECKMATE:
7736                 reason = "Xboard adjudication: Checkmate";
7737                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7738                 break;
7739             }
7740
7741                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7742                     case EP_STALEMATE:
7743                         result = GameIsDrawn; break;
7744                     case EP_CHECKMATE:
7745                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7746                     case EP_WINS:
7747                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7748                     default:
7749                         result = EndOfFile;
7750                 }
7751                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7752                     if(engineOpponent)
7753                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7754                     GameEnds( result, reason, GE_XBOARD );
7755                     return 1;
7756                 }
7757
7758                 /* Next absolutely insufficient mating material. */
7759                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7760                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7761                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7762
7763                      /* always flag draws, for judging claims */
7764                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7765
7766                      if(canAdjudicate && appData.materialDraws) {
7767                          /* but only adjudicate them if adjudication enabled */
7768                          if(engineOpponent) {
7769                            SendToProgram("force\n", engineOpponent); // suppress reply
7770                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7771                          }
7772                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7773                          return 1;
7774                      }
7775                 }
7776
7777                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7778                 if(gameInfo.variant == VariantXiangqi ?
7779                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7780                  : nrW + nrB == 4 &&
7781                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7782                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7783                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7784                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7785                    ) ) {
7786                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7787                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7788                           if(engineOpponent) {
7789                             SendToProgram("force\n", engineOpponent); // suppress reply
7790                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7791                           }
7792                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7793                           return 1;
7794                      }
7795                 } else moveCount = 6;
7796             }
7797
7798         // Repetition draws and 50-move rule can be applied independently of legality testing
7799
7800                 /* Check for rep-draws */
7801                 count = 0;
7802                 for(k = forwardMostMove-2;
7803                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7804                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7805                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7806                     k-=2)
7807                 {   int rights=0;
7808                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7809                         /* compare castling rights */
7810                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7811                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7812                                 rights++; /* King lost rights, while rook still had them */
7813                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7814                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7815                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7816                                    rights++; /* but at least one rook lost them */
7817                         }
7818                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7819                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7820                                 rights++;
7821                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7822                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7823                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7824                                    rights++;
7825                         }
7826                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7827                             && appData.drawRepeats > 1) {
7828                              /* adjudicate after user-specified nr of repeats */
7829                              int result = GameIsDrawn;
7830                              char *details = "XBoard adjudication: repetition draw";
7831                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7832                                 // [HGM] xiangqi: check for forbidden perpetuals
7833                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7834                                 for(m=forwardMostMove; m>k; m-=2) {
7835                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7836                                         ourPerpetual = 0; // the current mover did not always check
7837                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7838                                         hisPerpetual = 0; // the opponent did not always check
7839                                 }
7840                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7841                                                                         ourPerpetual, hisPerpetual);
7842                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7843                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7844                                     details = "Xboard adjudication: perpetual checking";
7845                                 } else
7846                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7847                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7848                                 } else
7849                                 // Now check for perpetual chases
7850                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7851                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7852                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7853                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7854                                         static char resdet[MSG_SIZ];
7855                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7856                                         details = resdet;
7857                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7858                                     } else
7859                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7860                                         break; // Abort repetition-checking loop.
7861                                 }
7862                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7863                              }
7864                              if(engineOpponent) {
7865                                SendToProgram("force\n", engineOpponent); // suppress reply
7866                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7867                              }
7868                              GameEnds( result, details, GE_XBOARD );
7869                              return 1;
7870                         }
7871                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7872                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7873                     }
7874                 }
7875
7876                 /* Now we test for 50-move draws. Determine ply count */
7877                 count = forwardMostMove;
7878                 /* look for last irreversble move */
7879                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7880                     count--;
7881                 /* if we hit starting position, add initial plies */
7882                 if( count == backwardMostMove )
7883                     count -= initialRulePlies;
7884                 count = forwardMostMove - count;
7885                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7886                         // adjust reversible move counter for checks in Xiangqi
7887                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7888                         if(i < backwardMostMove) i = backwardMostMove;
7889                         while(i <= forwardMostMove) {
7890                                 lastCheck = inCheck; // check evasion does not count
7891                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7892                                 if(inCheck || lastCheck) count--; // check does not count
7893                                 i++;
7894                         }
7895                 }
7896                 if( count >= 100)
7897                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7898                          /* this is used to judge if draw claims are legal */
7899                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7900                          if(engineOpponent) {
7901                            SendToProgram("force\n", engineOpponent); // suppress reply
7902                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7903                          }
7904                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7905                          return 1;
7906                 }
7907
7908                 /* if draw offer is pending, treat it as a draw claim
7909                  * when draw condition present, to allow engines a way to
7910                  * claim draws before making their move to avoid a race
7911                  * condition occurring after their move
7912                  */
7913                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7914                          char *p = NULL;
7915                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7916                              p = "Draw claim: 50-move rule";
7917                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7918                              p = "Draw claim: 3-fold repetition";
7919                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7920                              p = "Draw claim: insufficient mating material";
7921                          if( p != NULL && canAdjudicate) {
7922                              if(engineOpponent) {
7923                                SendToProgram("force\n", engineOpponent); // suppress reply
7924                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7925                              }
7926                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7927                              return 1;
7928                          }
7929                 }
7930
7931                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7932                     if(engineOpponent) {
7933                       SendToProgram("force\n", engineOpponent); // suppress reply
7934                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7935                     }
7936                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7937                     return 1;
7938                 }
7939         return 0;
7940 }
7941
7942 char *
7943 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7944 {   // [HGM] book: this routine intercepts moves to simulate book replies
7945     char *bookHit = NULL;
7946
7947     //first determine if the incoming move brings opponent into his book
7948     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7949         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7950     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7951     if(bookHit != NULL && !cps->bookSuspend) {
7952         // make sure opponent is not going to reply after receiving move to book position
7953         SendToProgram("force\n", cps);
7954         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7955     }
7956     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7957     // now arrange restart after book miss
7958     if(bookHit) {
7959         // after a book hit we never send 'go', and the code after the call to this routine
7960         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7961         char buf[MSG_SIZ], *move = bookHit;
7962         if(cps->useSAN) {
7963             int fromX, fromY, toX, toY;
7964             char promoChar;
7965             ChessMove moveType;
7966             move = buf + 30;
7967             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7968                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7969                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7970                                     PosFlags(forwardMostMove),
7971                                     fromY, fromX, toY, toX, promoChar, move);
7972             } else {
7973                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7974                 bookHit = NULL;
7975             }
7976         }
7977         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7978         SendToProgram(buf, cps);
7979         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7980     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7981         SendToProgram("go\n", cps);
7982         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7983     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7984         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7985             SendToProgram("go\n", cps);
7986         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7987     }
7988     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7989 }
7990
7991 int
7992 LoadError (char *errmess, ChessProgramState *cps)
7993 {   // unloads engine and switches back to -ncp mode if it was first
7994     if(cps->initDone) return FALSE;
7995     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7996     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7997     cps->pr = NoProc; 
7998     if(cps == &first) {
7999         appData.noChessProgram = TRUE;
8000         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8001         gameMode = BeginningOfGame; ModeHighlight();
8002         SetNCPMode();
8003     }
8004     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8005     DisplayMessage("", ""); // erase waiting message
8006     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8007     return TRUE;
8008 }
8009
8010 char *savedMessage;
8011 ChessProgramState *savedState;
8012 void
8013 DeferredBookMove (void)
8014 {
8015         if(savedState->lastPing != savedState->lastPong)
8016                     ScheduleDelayedEvent(DeferredBookMove, 10);
8017         else
8018         HandleMachineMove(savedMessage, savedState);
8019 }
8020
8021 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8022
8023 void
8024 HandleMachineMove (char *message, ChessProgramState *cps)
8025 {
8026     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8027     char realname[MSG_SIZ];
8028     int fromX, fromY, toX, toY;
8029     ChessMove moveType;
8030     char promoChar;
8031     char *p, *pv=buf1;
8032     int machineWhite, oldError;
8033     char *bookHit;
8034
8035     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8036         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8037         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8038             DisplayError(_("Invalid pairing from pairing engine"), 0);
8039             return;
8040         }
8041         pairingReceived = 1;
8042         NextMatchGame();
8043         return; // Skim the pairing messages here.
8044     }
8045
8046     oldError = cps->userError; cps->userError = 0;
8047
8048 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8049     /*
8050      * Kludge to ignore BEL characters
8051      */
8052     while (*message == '\007') message++;
8053
8054     /*
8055      * [HGM] engine debug message: ignore lines starting with '#' character
8056      */
8057     if(cps->debug && *message == '#') return;
8058
8059     /*
8060      * Look for book output
8061      */
8062     if (cps == &first && bookRequested) {
8063         if (message[0] == '\t' || message[0] == ' ') {
8064             /* Part of the book output is here; append it */
8065             strcat(bookOutput, message);
8066             strcat(bookOutput, "  \n");
8067             return;
8068         } else if (bookOutput[0] != NULLCHAR) {
8069             /* All of book output has arrived; display it */
8070             char *p = bookOutput;
8071             while (*p != NULLCHAR) {
8072                 if (*p == '\t') *p = ' ';
8073                 p++;
8074             }
8075             DisplayInformation(bookOutput);
8076             bookRequested = FALSE;
8077             /* Fall through to parse the current output */
8078         }
8079     }
8080
8081     /*
8082      * Look for machine move.
8083      */
8084     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8085         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8086     {
8087         /* This method is only useful on engines that support ping */
8088         if (cps->lastPing != cps->lastPong) {
8089           if (gameMode == BeginningOfGame) {
8090             /* Extra move from before last new; ignore */
8091             if (appData.debugMode) {
8092                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8093             }
8094           } else {
8095             if (appData.debugMode) {
8096                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8097                         cps->which, gameMode);
8098             }
8099
8100             SendToProgram("undo\n", cps);
8101           }
8102           return;
8103         }
8104
8105         switch (gameMode) {
8106           case BeginningOfGame:
8107             /* Extra move from before last reset; ignore */
8108             if (appData.debugMode) {
8109                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8110             }
8111             return;
8112
8113           case EndOfGame:
8114           case IcsIdle:
8115           default:
8116             /* Extra move after we tried to stop.  The mode test is
8117                not a reliable way of detecting this problem, but it's
8118                the best we can do on engines that don't support ping.
8119             */
8120             if (appData.debugMode) {
8121                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8122                         cps->which, gameMode);
8123             }
8124             SendToProgram("undo\n", cps);
8125             return;
8126
8127           case MachinePlaysWhite:
8128           case IcsPlayingWhite:
8129             machineWhite = TRUE;
8130             break;
8131
8132           case MachinePlaysBlack:
8133           case IcsPlayingBlack:
8134             machineWhite = FALSE;
8135             break;
8136
8137           case TwoMachinesPlay:
8138             machineWhite = (cps->twoMachinesColor[0] == 'w');
8139             break;
8140         }
8141         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8142             if (appData.debugMode) {
8143                 fprintf(debugFP,
8144                         "Ignoring move out of turn by %s, gameMode %d"
8145                         ", forwardMost %d\n",
8146                         cps->which, gameMode, forwardMostMove);
8147             }
8148             return;
8149         }
8150
8151         if(cps->alphaRank) AlphaRank(machineMove, 4);
8152         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8153                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8154             /* Machine move could not be parsed; ignore it. */
8155           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8156                     machineMove, _(cps->which));
8157             DisplayError(buf1, 0);
8158             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8159                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8160             if (gameMode == TwoMachinesPlay) {
8161               GameEnds(machineWhite ? BlackWins : WhiteWins,
8162                        buf1, GE_XBOARD);
8163             }
8164             return;
8165         }
8166
8167         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8168         /* So we have to redo legality test with true e.p. status here,  */
8169         /* to make sure an illegal e.p. capture does not slip through,   */
8170         /* to cause a forfeit on a justified illegal-move complaint      */
8171         /* of the opponent.                                              */
8172         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8173            ChessMove moveType;
8174            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8175                              fromY, fromX, toY, toX, promoChar);
8176             if(moveType == IllegalMove) {
8177               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8178                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8179                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8180                            buf1, GE_XBOARD);
8181                 return;
8182            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8183            /* [HGM] Kludge to handle engines that send FRC-style castling
8184               when they shouldn't (like TSCP-Gothic) */
8185            switch(moveType) {
8186              case WhiteASideCastleFR:
8187              case BlackASideCastleFR:
8188                toX+=2;
8189                currentMoveString[2]++;
8190                break;
8191              case WhiteHSideCastleFR:
8192              case BlackHSideCastleFR:
8193                toX--;
8194                currentMoveString[2]--;
8195                break;
8196              default: ; // nothing to do, but suppresses warning of pedantic compilers
8197            }
8198         }
8199         hintRequested = FALSE;
8200         lastHint[0] = NULLCHAR;
8201         bookRequested = FALSE;
8202         /* Program may be pondering now */
8203         cps->maybeThinking = TRUE;
8204         if (cps->sendTime == 2) cps->sendTime = 1;
8205         if (cps->offeredDraw) cps->offeredDraw--;
8206
8207         /* [AS] Save move info*/
8208         pvInfoList[ forwardMostMove ].score = programStats.score;
8209         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8210         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8211
8212         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8213
8214         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8215         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8216             int count = 0;
8217
8218             while( count < adjudicateLossPlies ) {
8219                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8220
8221                 if( count & 1 ) {
8222                     score = -score; /* Flip score for winning side */
8223                 }
8224
8225                 if( score > adjudicateLossThreshold ) {
8226                     break;
8227                 }
8228
8229                 count++;
8230             }
8231
8232             if( count >= adjudicateLossPlies ) {
8233                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8234
8235                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8236                     "Xboard adjudication",
8237                     GE_XBOARD );
8238
8239                 return;
8240             }
8241         }
8242
8243         if(Adjudicate(cps)) {
8244             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8245             return; // [HGM] adjudicate: for all automatic game ends
8246         }
8247
8248 #if ZIPPY
8249         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8250             first.initDone) {
8251           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8252                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8253                 SendToICS("draw ");
8254                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8255           }
8256           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8257           ics_user_moved = 1;
8258           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8259                 char buf[3*MSG_SIZ];
8260
8261                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8262                         programStats.score / 100.,
8263                         programStats.depth,
8264                         programStats.time / 100.,
8265                         (unsigned int)programStats.nodes,
8266                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8267                         programStats.movelist);
8268                 SendToICS(buf);
8269 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8270           }
8271         }
8272 #endif
8273
8274         /* [AS] Clear stats for next move */
8275         ClearProgramStats();
8276         thinkOutput[0] = NULLCHAR;
8277         hiddenThinkOutputState = 0;
8278
8279         bookHit = NULL;
8280         if (gameMode == TwoMachinesPlay) {
8281             /* [HGM] relaying draw offers moved to after reception of move */
8282             /* and interpreting offer as claim if it brings draw condition */
8283             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8284                 SendToProgram("draw\n", cps->other);
8285             }
8286             if (cps->other->sendTime) {
8287                 SendTimeRemaining(cps->other,
8288                                   cps->other->twoMachinesColor[0] == 'w');
8289             }
8290             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8291             if (firstMove && !bookHit) {
8292                 firstMove = FALSE;
8293                 if (cps->other->useColors) {
8294                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8295                 }
8296                 SendToProgram("go\n", cps->other);
8297             }
8298             cps->other->maybeThinking = TRUE;
8299         }
8300
8301         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8302
8303         if (!pausing && appData.ringBellAfterMoves) {
8304             RingBell();
8305         }
8306
8307         /*
8308          * Reenable menu items that were disabled while
8309          * machine was thinking
8310          */
8311         if (gameMode != TwoMachinesPlay)
8312             SetUserThinkingEnables();
8313
8314         // [HGM] book: after book hit opponent has received move and is now in force mode
8315         // force the book reply into it, and then fake that it outputted this move by jumping
8316         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8317         if(bookHit) {
8318                 static char bookMove[MSG_SIZ]; // a bit generous?
8319
8320                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8321                 strcat(bookMove, bookHit);
8322                 message = bookMove;
8323                 cps = cps->other;
8324                 programStats.nodes = programStats.depth = programStats.time =
8325                 programStats.score = programStats.got_only_move = 0;
8326                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8327
8328                 if(cps->lastPing != cps->lastPong) {
8329                     savedMessage = message; // args for deferred call
8330                     savedState = cps;
8331                     ScheduleDelayedEvent(DeferredBookMove, 10);
8332                     return;
8333                 }
8334                 goto FakeBookMove;
8335         }
8336
8337         return;
8338     }
8339
8340     /* Set special modes for chess engines.  Later something general
8341      *  could be added here; for now there is just one kludge feature,
8342      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8343      *  when "xboard" is given as an interactive command.
8344      */
8345     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8346         cps->useSigint = FALSE;
8347         cps->useSigterm = FALSE;
8348     }
8349     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8350       ParseFeatures(message+8, cps);
8351       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8352     }
8353
8354     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8355                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8356       int dummy, s=6; char buf[MSG_SIZ];
8357       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8358       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8359       if(startedFromSetupPosition) return;
8360       ParseFEN(boards[0], &dummy, message+s);
8361       DrawPosition(TRUE, boards[0]);
8362       startedFromSetupPosition = TRUE;
8363       return;
8364     }
8365     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8366      * want this, I was asked to put it in, and obliged.
8367      */
8368     if (!strncmp(message, "setboard ", 9)) {
8369         Board initial_position;
8370
8371         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8372
8373         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8374             DisplayError(_("Bad FEN received from engine"), 0);
8375             return ;
8376         } else {
8377            Reset(TRUE, FALSE);
8378            CopyBoard(boards[0], initial_position);
8379            initialRulePlies = FENrulePlies;
8380            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8381            else gameMode = MachinePlaysBlack;
8382            DrawPosition(FALSE, boards[currentMove]);
8383         }
8384         return;
8385     }
8386
8387     /*
8388      * Look for communication commands
8389      */
8390     if (!strncmp(message, "telluser ", 9)) {
8391         if(message[9] == '\\' && message[10] == '\\')
8392             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8393         PlayTellSound();
8394         DisplayNote(message + 9);
8395         return;
8396     }
8397     if (!strncmp(message, "tellusererror ", 14)) {
8398         cps->userError = 1;
8399         if(message[14] == '\\' && message[15] == '\\')
8400             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8401         PlayTellSound();
8402         DisplayError(message + 14, 0);
8403         return;
8404     }
8405     if (!strncmp(message, "tellopponent ", 13)) {
8406       if (appData.icsActive) {
8407         if (loggedOn) {
8408           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8409           SendToICS(buf1);
8410         }
8411       } else {
8412         DisplayNote(message + 13);
8413       }
8414       return;
8415     }
8416     if (!strncmp(message, "tellothers ", 11)) {
8417       if (appData.icsActive) {
8418         if (loggedOn) {
8419           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8420           SendToICS(buf1);
8421         }
8422       }
8423       return;
8424     }
8425     if (!strncmp(message, "tellall ", 8)) {
8426       if (appData.icsActive) {
8427         if (loggedOn) {
8428           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8429           SendToICS(buf1);
8430         }
8431       } else {
8432         DisplayNote(message + 8);
8433       }
8434       return;
8435     }
8436     if (strncmp(message, "warning", 7) == 0) {
8437         /* Undocumented feature, use tellusererror in new code */
8438         DisplayError(message, 0);
8439         return;
8440     }
8441     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8442         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8443         strcat(realname, " query");
8444         AskQuestion(realname, buf2, buf1, cps->pr);
8445         return;
8446     }
8447     /* Commands from the engine directly to ICS.  We don't allow these to be
8448      *  sent until we are logged on. Crafty kibitzes have been known to
8449      *  interfere with the login process.
8450      */
8451     if (loggedOn) {
8452         if (!strncmp(message, "tellics ", 8)) {
8453             SendToICS(message + 8);
8454             SendToICS("\n");
8455             return;
8456         }
8457         if (!strncmp(message, "tellicsnoalias ", 15)) {
8458             SendToICS(ics_prefix);
8459             SendToICS(message + 15);
8460             SendToICS("\n");
8461             return;
8462         }
8463         /* The following are for backward compatibility only */
8464         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8465             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8466             SendToICS(ics_prefix);
8467             SendToICS(message);
8468             SendToICS("\n");
8469             return;
8470         }
8471     }
8472     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8473         return;
8474     }
8475     /*
8476      * If the move is illegal, cancel it and redraw the board.
8477      * Also deal with other error cases.  Matching is rather loose
8478      * here to accommodate engines written before the spec.
8479      */
8480     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8481         strncmp(message, "Error", 5) == 0) {
8482         if (StrStr(message, "name") ||
8483             StrStr(message, "rating") || StrStr(message, "?") ||
8484             StrStr(message, "result") || StrStr(message, "board") ||
8485             StrStr(message, "bk") || StrStr(message, "computer") ||
8486             StrStr(message, "variant") || StrStr(message, "hint") ||
8487             StrStr(message, "random") || StrStr(message, "depth") ||
8488             StrStr(message, "accepted")) {
8489             return;
8490         }
8491         if (StrStr(message, "protover")) {
8492           /* Program is responding to input, so it's apparently done
8493              initializing, and this error message indicates it is
8494              protocol version 1.  So we don't need to wait any longer
8495              for it to initialize and send feature commands. */
8496           FeatureDone(cps, 1);
8497           cps->protocolVersion = 1;
8498           return;
8499         }
8500         cps->maybeThinking = FALSE;
8501
8502         if (StrStr(message, "draw")) {
8503             /* Program doesn't have "draw" command */
8504             cps->sendDrawOffers = 0;
8505             return;
8506         }
8507         if (cps->sendTime != 1 &&
8508             (StrStr(message, "time") || StrStr(message, "otim"))) {
8509           /* Program apparently doesn't have "time" or "otim" command */
8510           cps->sendTime = 0;
8511           return;
8512         }
8513         if (StrStr(message, "analyze")) {
8514             cps->analysisSupport = FALSE;
8515             cps->analyzing = FALSE;
8516 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8517             EditGameEvent(); // [HGM] try to preserve loaded game
8518             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8519             DisplayError(buf2, 0);
8520             return;
8521         }
8522         if (StrStr(message, "(no matching move)st")) {
8523           /* Special kludge for GNU Chess 4 only */
8524           cps->stKludge = TRUE;
8525           SendTimeControl(cps, movesPerSession, timeControl,
8526                           timeIncrement, appData.searchDepth,
8527                           searchTime);
8528           return;
8529         }
8530         if (StrStr(message, "(no matching move)sd")) {
8531           /* Special kludge for GNU Chess 4 only */
8532           cps->sdKludge = TRUE;
8533           SendTimeControl(cps, movesPerSession, timeControl,
8534                           timeIncrement, appData.searchDepth,
8535                           searchTime);
8536           return;
8537         }
8538         if (!StrStr(message, "llegal")) {
8539             return;
8540         }
8541         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8542             gameMode == IcsIdle) return;
8543         if (forwardMostMove <= backwardMostMove) return;
8544         if (pausing) PauseEvent();
8545       if(appData.forceIllegal) {
8546             // [HGM] illegal: machine refused move; force position after move into it
8547           SendToProgram("force\n", cps);
8548           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8549                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8550                 // when black is to move, while there might be nothing on a2 or black
8551                 // might already have the move. So send the board as if white has the move.
8552                 // But first we must change the stm of the engine, as it refused the last move
8553                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8554                 if(WhiteOnMove(forwardMostMove)) {
8555                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8556                     SendBoard(cps, forwardMostMove); // kludgeless board
8557                 } else {
8558                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8559                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8560                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8561                 }
8562           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8563             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8564                  gameMode == TwoMachinesPlay)
8565               SendToProgram("go\n", cps);
8566             return;
8567       } else
8568         if (gameMode == PlayFromGameFile) {
8569             /* Stop reading this game file */
8570             gameMode = EditGame;
8571             ModeHighlight();
8572         }
8573         /* [HGM] illegal-move claim should forfeit game when Xboard */
8574         /* only passes fully legal moves                            */
8575         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8576             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8577                                 "False illegal-move claim", GE_XBOARD );
8578             return; // do not take back move we tested as valid
8579         }
8580         currentMove = forwardMostMove-1;
8581         DisplayMove(currentMove-1); /* before DisplayMoveError */
8582         SwitchClocks(forwardMostMove-1); // [HGM] race
8583         DisplayBothClocks();
8584         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8585                 parseList[currentMove], _(cps->which));
8586         DisplayMoveError(buf1);
8587         DrawPosition(FALSE, boards[currentMove]);
8588
8589         SetUserThinkingEnables();
8590         return;
8591     }
8592     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8593         /* Program has a broken "time" command that
8594            outputs a string not ending in newline.
8595            Don't use it. */
8596         cps->sendTime = 0;
8597     }
8598
8599     /*
8600      * If chess program startup fails, exit with an error message.
8601      * Attempts to recover here are futile. [HGM] Well, we try anyway
8602      */
8603     if ((StrStr(message, "unknown host") != NULL)
8604         || (StrStr(message, "No remote directory") != NULL)
8605         || (StrStr(message, "not found") != NULL)
8606         || (StrStr(message, "No such file") != NULL)
8607         || (StrStr(message, "can't alloc") != NULL)
8608         || (StrStr(message, "Permission denied") != NULL)) {
8609
8610         cps->maybeThinking = FALSE;
8611         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8612                 _(cps->which), cps->program, cps->host, message);
8613         RemoveInputSource(cps->isr);
8614         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8615             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8616             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8617         }
8618         return;
8619     }
8620
8621     /*
8622      * Look for hint output
8623      */
8624     if (sscanf(message, "Hint: %s", buf1) == 1) {
8625         if (cps == &first && hintRequested) {
8626             hintRequested = FALSE;
8627             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8628                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8629                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8630                                     PosFlags(forwardMostMove),
8631                                     fromY, fromX, toY, toX, promoChar, buf1);
8632                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8633                 DisplayInformation(buf2);
8634             } else {
8635                 /* Hint move could not be parsed!? */
8636               snprintf(buf2, sizeof(buf2),
8637                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8638                         buf1, _(cps->which));
8639                 DisplayError(buf2, 0);
8640             }
8641         } else {
8642           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8643         }
8644         return;
8645     }
8646
8647     /*
8648      * Ignore other messages if game is not in progress
8649      */
8650     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8651         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8652
8653     /*
8654      * look for win, lose, draw, or draw offer
8655      */
8656     if (strncmp(message, "1-0", 3) == 0) {
8657         char *p, *q, *r = "";
8658         p = strchr(message, '{');
8659         if (p) {
8660             q = strchr(p, '}');
8661             if (q) {
8662                 *q = NULLCHAR;
8663                 r = p + 1;
8664             }
8665         }
8666         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8667         return;
8668     } else if (strncmp(message, "0-1", 3) == 0) {
8669         char *p, *q, *r = "";
8670         p = strchr(message, '{');
8671         if (p) {
8672             q = strchr(p, '}');
8673             if (q) {
8674                 *q = NULLCHAR;
8675                 r = p + 1;
8676             }
8677         }
8678         /* Kludge for Arasan 4.1 bug */
8679         if (strcmp(r, "Black resigns") == 0) {
8680             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8681             return;
8682         }
8683         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8684         return;
8685     } else if (strncmp(message, "1/2", 3) == 0) {
8686         char *p, *q, *r = "";
8687         p = strchr(message, '{');
8688         if (p) {
8689             q = strchr(p, '}');
8690             if (q) {
8691                 *q = NULLCHAR;
8692                 r = p + 1;
8693             }
8694         }
8695
8696         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8697         return;
8698
8699     } else if (strncmp(message, "White resign", 12) == 0) {
8700         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8701         return;
8702     } else if (strncmp(message, "Black resign", 12) == 0) {
8703         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8704         return;
8705     } else if (strncmp(message, "White matches", 13) == 0 ||
8706                strncmp(message, "Black matches", 13) == 0   ) {
8707         /* [HGM] ignore GNUShogi noises */
8708         return;
8709     } else if (strncmp(message, "White", 5) == 0 &&
8710                message[5] != '(' &&
8711                StrStr(message, "Black") == NULL) {
8712         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8713         return;
8714     } else if (strncmp(message, "Black", 5) == 0 &&
8715                message[5] != '(') {
8716         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8717         return;
8718     } else if (strcmp(message, "resign") == 0 ||
8719                strcmp(message, "computer resigns") == 0) {
8720         switch (gameMode) {
8721           case MachinePlaysBlack:
8722           case IcsPlayingBlack:
8723             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8724             break;
8725           case MachinePlaysWhite:
8726           case IcsPlayingWhite:
8727             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8728             break;
8729           case TwoMachinesPlay:
8730             if (cps->twoMachinesColor[0] == 'w')
8731               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8732             else
8733               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8734             break;
8735           default:
8736             /* can't happen */
8737             break;
8738         }
8739         return;
8740     } else if (strncmp(message, "opponent mates", 14) == 0) {
8741         switch (gameMode) {
8742           case MachinePlaysBlack:
8743           case IcsPlayingBlack:
8744             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8745             break;
8746           case MachinePlaysWhite:
8747           case IcsPlayingWhite:
8748             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8749             break;
8750           case TwoMachinesPlay:
8751             if (cps->twoMachinesColor[0] == 'w')
8752               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8753             else
8754               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8755             break;
8756           default:
8757             /* can't happen */
8758             break;
8759         }
8760         return;
8761     } else if (strncmp(message, "computer mates", 14) == 0) {
8762         switch (gameMode) {
8763           case MachinePlaysBlack:
8764           case IcsPlayingBlack:
8765             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8766             break;
8767           case MachinePlaysWhite:
8768           case IcsPlayingWhite:
8769             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8770             break;
8771           case TwoMachinesPlay:
8772             if (cps->twoMachinesColor[0] == 'w')
8773               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8774             else
8775               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8776             break;
8777           default:
8778             /* can't happen */
8779             break;
8780         }
8781         return;
8782     } else if (strncmp(message, "checkmate", 9) == 0) {
8783         if (WhiteOnMove(forwardMostMove)) {
8784             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8785         } else {
8786             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8787         }
8788         return;
8789     } else if (strstr(message, "Draw") != NULL ||
8790                strstr(message, "game is a draw") != NULL) {
8791         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8792         return;
8793     } else if (strstr(message, "offer") != NULL &&
8794                strstr(message, "draw") != NULL) {
8795 #if ZIPPY
8796         if (appData.zippyPlay && first.initDone) {
8797             /* Relay offer to ICS */
8798             SendToICS(ics_prefix);
8799             SendToICS("draw\n");
8800         }
8801 #endif
8802         cps->offeredDraw = 2; /* valid until this engine moves twice */
8803         if (gameMode == TwoMachinesPlay) {
8804             if (cps->other->offeredDraw) {
8805                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8806             /* [HGM] in two-machine mode we delay relaying draw offer      */
8807             /* until after we also have move, to see if it is really claim */
8808             }
8809         } else if (gameMode == MachinePlaysWhite ||
8810                    gameMode == MachinePlaysBlack) {
8811           if (userOfferedDraw) {
8812             DisplayInformation(_("Machine accepts your draw offer"));
8813             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8814           } else {
8815             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8816           }
8817         }
8818     }
8819
8820
8821     /*
8822      * Look for thinking output
8823      */
8824     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8825           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8826                                 ) {
8827         int plylev, mvleft, mvtot, curscore, time;
8828         char mvname[MOVE_LEN];
8829         u64 nodes; // [DM]
8830         char plyext;
8831         int ignore = FALSE;
8832         int prefixHint = FALSE;
8833         mvname[0] = NULLCHAR;
8834
8835         switch (gameMode) {
8836           case MachinePlaysBlack:
8837           case IcsPlayingBlack:
8838             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8839             break;
8840           case MachinePlaysWhite:
8841           case IcsPlayingWhite:
8842             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8843             break;
8844           case AnalyzeMode:
8845           case AnalyzeFile:
8846             break;
8847           case IcsObserving: /* [DM] icsEngineAnalyze */
8848             if (!appData.icsEngineAnalyze) ignore = TRUE;
8849             break;
8850           case TwoMachinesPlay:
8851             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8852                 ignore = TRUE;
8853             }
8854             break;
8855           default:
8856             ignore = TRUE;
8857             break;
8858         }
8859
8860         if (!ignore) {
8861             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8862             buf1[0] = NULLCHAR;
8863             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8864                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8865
8866                 if (plyext != ' ' && plyext != '\t') {
8867                     time *= 100;
8868                 }
8869
8870                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8871                 if( cps->scoreIsAbsolute &&
8872                     ( gameMode == MachinePlaysBlack ||
8873                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8874                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8875                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8876                      !WhiteOnMove(currentMove)
8877                     ) )
8878                 {
8879                     curscore = -curscore;
8880                 }
8881
8882                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8883
8884                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8885                         char buf[MSG_SIZ];
8886                         FILE *f;
8887                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8888                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8889                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8890                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8891                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8892                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8893                                 fclose(f);
8894                         } else DisplayError(_("failed writing PV"), 0);
8895                 }
8896
8897                 tempStats.depth = plylev;
8898                 tempStats.nodes = nodes;
8899                 tempStats.time = time;
8900                 tempStats.score = curscore;
8901                 tempStats.got_only_move = 0;
8902
8903                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8904                         int ticklen;
8905
8906                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8907                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8908                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8909                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8910                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8911                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8912                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8913                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8914                 }
8915
8916                 /* Buffer overflow protection */
8917                 if (pv[0] != NULLCHAR) {
8918                     if (strlen(pv) >= sizeof(tempStats.movelist)
8919                         && appData.debugMode) {
8920                         fprintf(debugFP,
8921                                 "PV is too long; using the first %u bytes.\n",
8922                                 (unsigned) sizeof(tempStats.movelist) - 1);
8923                     }
8924
8925                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8926                 } else {
8927                     sprintf(tempStats.movelist, " no PV\n");
8928                 }
8929
8930                 if (tempStats.seen_stat) {
8931                     tempStats.ok_to_send = 1;
8932                 }
8933
8934                 if (strchr(tempStats.movelist, '(') != NULL) {
8935                     tempStats.line_is_book = 1;
8936                     tempStats.nr_moves = 0;
8937                     tempStats.moves_left = 0;
8938                 } else {
8939                     tempStats.line_is_book = 0;
8940                 }
8941
8942                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8943                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8944
8945                 SendProgramStatsToFrontend( cps, &tempStats );
8946
8947                 /*
8948                     [AS] Protect the thinkOutput buffer from overflow... this
8949                     is only useful if buf1 hasn't overflowed first!
8950                 */
8951                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8952                          plylev,
8953                          (gameMode == TwoMachinesPlay ?
8954                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8955                          ((double) curscore) / 100.0,
8956                          prefixHint ? lastHint : "",
8957                          prefixHint ? " " : "" );
8958
8959                 if( buf1[0] != NULLCHAR ) {
8960                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8961
8962                     if( strlen(pv) > max_len ) {
8963                         if( appData.debugMode) {
8964                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8965                         }
8966                         pv[max_len+1] = '\0';
8967                     }
8968
8969                     strcat( thinkOutput, pv);
8970                 }
8971
8972                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8973                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8974                     DisplayMove(currentMove - 1);
8975                 }
8976                 return;
8977
8978             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8979                 /* crafty (9.25+) says "(only move) <move>"
8980                  * if there is only 1 legal move
8981                  */
8982                 sscanf(p, "(only move) %s", buf1);
8983                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8984                 sprintf(programStats.movelist, "%s (only move)", buf1);
8985                 programStats.depth = 1;
8986                 programStats.nr_moves = 1;
8987                 programStats.moves_left = 1;
8988                 programStats.nodes = 1;
8989                 programStats.time = 1;
8990                 programStats.got_only_move = 1;
8991
8992                 /* Not really, but we also use this member to
8993                    mean "line isn't going to change" (Crafty
8994                    isn't searching, so stats won't change) */
8995                 programStats.line_is_book = 1;
8996
8997                 SendProgramStatsToFrontend( cps, &programStats );
8998
8999                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9000                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9001                     DisplayMove(currentMove - 1);
9002                 }
9003                 return;
9004             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9005                               &time, &nodes, &plylev, &mvleft,
9006                               &mvtot, mvname) >= 5) {
9007                 /* The stat01: line is from Crafty (9.29+) in response
9008                    to the "." command */
9009                 programStats.seen_stat = 1;
9010                 cps->maybeThinking = TRUE;
9011
9012                 if (programStats.got_only_move || !appData.periodicUpdates)
9013                   return;
9014
9015                 programStats.depth = plylev;
9016                 programStats.time = time;
9017                 programStats.nodes = nodes;
9018                 programStats.moves_left = mvleft;
9019                 programStats.nr_moves = mvtot;
9020                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9021                 programStats.ok_to_send = 1;
9022                 programStats.movelist[0] = '\0';
9023
9024                 SendProgramStatsToFrontend( cps, &programStats );
9025
9026                 return;
9027
9028             } else if (strncmp(message,"++",2) == 0) {
9029                 /* Crafty 9.29+ outputs this */
9030                 programStats.got_fail = 2;
9031                 return;
9032
9033             } else if (strncmp(message,"--",2) == 0) {
9034                 /* Crafty 9.29+ outputs this */
9035                 programStats.got_fail = 1;
9036                 return;
9037
9038             } else if (thinkOutput[0] != NULLCHAR &&
9039                        strncmp(message, "    ", 4) == 0) {
9040                 unsigned message_len;
9041
9042                 p = message;
9043                 while (*p && *p == ' ') p++;
9044
9045                 message_len = strlen( p );
9046
9047                 /* [AS] Avoid buffer overflow */
9048                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9049                     strcat(thinkOutput, " ");
9050                     strcat(thinkOutput, p);
9051                 }
9052
9053                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9054                     strcat(programStats.movelist, " ");
9055                     strcat(programStats.movelist, p);
9056                 }
9057
9058                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9059                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9060                     DisplayMove(currentMove - 1);
9061                 }
9062                 return;
9063             }
9064         }
9065         else {
9066             buf1[0] = NULLCHAR;
9067
9068             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9069                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9070             {
9071                 ChessProgramStats cpstats;
9072
9073                 if (plyext != ' ' && plyext != '\t') {
9074                     time *= 100;
9075                 }
9076
9077                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9078                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9079                     curscore = -curscore;
9080                 }
9081
9082                 cpstats.depth = plylev;
9083                 cpstats.nodes = nodes;
9084                 cpstats.time = time;
9085                 cpstats.score = curscore;
9086                 cpstats.got_only_move = 0;
9087                 cpstats.movelist[0] = '\0';
9088
9089                 if (buf1[0] != NULLCHAR) {
9090                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9091                 }
9092
9093                 cpstats.ok_to_send = 0;
9094                 cpstats.line_is_book = 0;
9095                 cpstats.nr_moves = 0;
9096                 cpstats.moves_left = 0;
9097
9098                 SendProgramStatsToFrontend( cps, &cpstats );
9099             }
9100         }
9101     }
9102 }
9103
9104
9105 /* Parse a game score from the character string "game", and
9106    record it as the history of the current game.  The game
9107    score is NOT assumed to start from the standard position.
9108    The display is not updated in any way.
9109    */
9110 void
9111 ParseGameHistory (char *game)
9112 {
9113     ChessMove moveType;
9114     int fromX, fromY, toX, toY, boardIndex;
9115     char promoChar;
9116     char *p, *q;
9117     char buf[MSG_SIZ];
9118
9119     if (appData.debugMode)
9120       fprintf(debugFP, "Parsing game history: %s\n", game);
9121
9122     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9123     gameInfo.site = StrSave(appData.icsHost);
9124     gameInfo.date = PGNDate();
9125     gameInfo.round = StrSave("-");
9126
9127     /* Parse out names of players */
9128     while (*game == ' ') game++;
9129     p = buf;
9130     while (*game != ' ') *p++ = *game++;
9131     *p = NULLCHAR;
9132     gameInfo.white = StrSave(buf);
9133     while (*game == ' ') game++;
9134     p = buf;
9135     while (*game != ' ' && *game != '\n') *p++ = *game++;
9136     *p = NULLCHAR;
9137     gameInfo.black = StrSave(buf);
9138
9139     /* Parse moves */
9140     boardIndex = blackPlaysFirst ? 1 : 0;
9141     yynewstr(game);
9142     for (;;) {
9143         yyboardindex = boardIndex;
9144         moveType = (ChessMove) Myylex();
9145         switch (moveType) {
9146           case IllegalMove:             /* maybe suicide chess, etc. */
9147   if (appData.debugMode) {
9148     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9149     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9150     setbuf(debugFP, NULL);
9151   }
9152           case WhitePromotion:
9153           case BlackPromotion:
9154           case WhiteNonPromotion:
9155           case BlackNonPromotion:
9156           case NormalMove:
9157           case WhiteCapturesEnPassant:
9158           case BlackCapturesEnPassant:
9159           case WhiteKingSideCastle:
9160           case WhiteQueenSideCastle:
9161           case BlackKingSideCastle:
9162           case BlackQueenSideCastle:
9163           case WhiteKingSideCastleWild:
9164           case WhiteQueenSideCastleWild:
9165           case BlackKingSideCastleWild:
9166           case BlackQueenSideCastleWild:
9167           /* PUSH Fabien */
9168           case WhiteHSideCastleFR:
9169           case WhiteASideCastleFR:
9170           case BlackHSideCastleFR:
9171           case BlackASideCastleFR:
9172           /* POP Fabien */
9173             fromX = currentMoveString[0] - AAA;
9174             fromY = currentMoveString[1] - ONE;
9175             toX = currentMoveString[2] - AAA;
9176             toY = currentMoveString[3] - ONE;
9177             promoChar = currentMoveString[4];
9178             break;
9179           case WhiteDrop:
9180           case BlackDrop:
9181             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9182             fromX = moveType == WhiteDrop ?
9183               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9184             (int) CharToPiece(ToLower(currentMoveString[0]));
9185             fromY = DROP_RANK;
9186             toX = currentMoveString[2] - AAA;
9187             toY = currentMoveString[3] - ONE;
9188             promoChar = NULLCHAR;
9189             break;
9190           case AmbiguousMove:
9191             /* bug? */
9192             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9193   if (appData.debugMode) {
9194     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9195     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9196     setbuf(debugFP, NULL);
9197   }
9198             DisplayError(buf, 0);
9199             return;
9200           case ImpossibleMove:
9201             /* bug? */
9202             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9203   if (appData.debugMode) {
9204     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9205     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9206     setbuf(debugFP, NULL);
9207   }
9208             DisplayError(buf, 0);
9209             return;
9210           case EndOfFile:
9211             if (boardIndex < backwardMostMove) {
9212                 /* Oops, gap.  How did that happen? */
9213                 DisplayError(_("Gap in move list"), 0);
9214                 return;
9215             }
9216             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9217             if (boardIndex > forwardMostMove) {
9218                 forwardMostMove = boardIndex;
9219             }
9220             return;
9221           case ElapsedTime:
9222             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9223                 strcat(parseList[boardIndex-1], " ");
9224                 strcat(parseList[boardIndex-1], yy_text);
9225             }
9226             continue;
9227           case Comment:
9228           case PGNTag:
9229           case NAG:
9230           default:
9231             /* ignore */
9232             continue;
9233           case WhiteWins:
9234           case BlackWins:
9235           case GameIsDrawn:
9236           case GameUnfinished:
9237             if (gameMode == IcsExamining) {
9238                 if (boardIndex < backwardMostMove) {
9239                     /* Oops, gap.  How did that happen? */
9240                     return;
9241                 }
9242                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9243                 return;
9244             }
9245             gameInfo.result = moveType;
9246             p = strchr(yy_text, '{');
9247             if (p == NULL) p = strchr(yy_text, '(');
9248             if (p == NULL) {
9249                 p = yy_text;
9250                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9251             } else {
9252                 q = strchr(p, *p == '{' ? '}' : ')');
9253                 if (q != NULL) *q = NULLCHAR;
9254                 p++;
9255             }
9256             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9257             gameInfo.resultDetails = StrSave(p);
9258             continue;
9259         }
9260         if (boardIndex >= forwardMostMove &&
9261             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9262             backwardMostMove = blackPlaysFirst ? 1 : 0;
9263             return;
9264         }
9265         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9266                                  fromY, fromX, toY, toX, promoChar,
9267                                  parseList[boardIndex]);
9268         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9269         /* currentMoveString is set as a side-effect of yylex */
9270         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9271         strcat(moveList[boardIndex], "\n");
9272         boardIndex++;
9273         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9274         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9275           case MT_NONE:
9276           case MT_STALEMATE:
9277           default:
9278             break;
9279           case MT_CHECK:
9280             if(gameInfo.variant != VariantShogi)
9281                 strcat(parseList[boardIndex - 1], "+");
9282             break;
9283           case MT_CHECKMATE:
9284           case MT_STAINMATE:
9285             strcat(parseList[boardIndex - 1], "#");
9286             break;
9287         }
9288     }
9289 }
9290
9291
9292 /* Apply a move to the given board  */
9293 void
9294 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9295 {
9296   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9297   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9298
9299     /* [HGM] compute & store e.p. status and castling rights for new position */
9300     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9301
9302       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9303       oldEP = (signed char)board[EP_STATUS];
9304       board[EP_STATUS] = EP_NONE;
9305
9306   if (fromY == DROP_RANK) {
9307         /* must be first */
9308         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9309             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9310             return;
9311         }
9312         piece = board[toY][toX] = (ChessSquare) fromX;
9313   } else {
9314       int i;
9315
9316       if( board[toY][toX] != EmptySquare )
9317            board[EP_STATUS] = EP_CAPTURE;
9318
9319       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9320            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9321                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9322       } else
9323       if( board[fromY][fromX] == WhitePawn ) {
9324            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9325                board[EP_STATUS] = EP_PAWN_MOVE;
9326            if( toY-fromY==2) {
9327                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9328                         gameInfo.variant != VariantBerolina || toX < fromX)
9329                       board[EP_STATUS] = toX | berolina;
9330                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9331                         gameInfo.variant != VariantBerolina || toX > fromX)
9332                       board[EP_STATUS] = toX;
9333            }
9334       } else
9335       if( board[fromY][fromX] == BlackPawn ) {
9336            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9337                board[EP_STATUS] = EP_PAWN_MOVE;
9338            if( toY-fromY== -2) {
9339                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9340                         gameInfo.variant != VariantBerolina || toX < fromX)
9341                       board[EP_STATUS] = toX | berolina;
9342                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9343                         gameInfo.variant != VariantBerolina || toX > fromX)
9344                       board[EP_STATUS] = toX;
9345            }
9346        }
9347
9348        for(i=0; i<nrCastlingRights; i++) {
9349            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9350               board[CASTLING][i] == toX   && castlingRank[i] == toY
9351              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9352        }
9353
9354        if(gameInfo.variant == VariantSChess) { // update virginity
9355            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9356            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9357            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9358            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9359        }
9360
9361      if (fromX == toX && fromY == toY) return;
9362
9363      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9364      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9365      if(gameInfo.variant == VariantKnightmate)
9366          king += (int) WhiteUnicorn - (int) WhiteKing;
9367
9368     /* Code added by Tord: */
9369     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9370     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9371         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9372       board[fromY][fromX] = EmptySquare;
9373       board[toY][toX] = EmptySquare;
9374       if((toX > fromX) != (piece == WhiteRook)) {
9375         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9376       } else {
9377         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9378       }
9379     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9380                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9381       board[fromY][fromX] = EmptySquare;
9382       board[toY][toX] = EmptySquare;
9383       if((toX > fromX) != (piece == BlackRook)) {
9384         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9385       } else {
9386         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9387       }
9388     /* End of code added by Tord */
9389
9390     } else if (board[fromY][fromX] == king
9391         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9392         && toY == fromY && toX > fromX+1) {
9393         board[fromY][fromX] = EmptySquare;
9394         board[toY][toX] = king;
9395         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9396         board[fromY][BOARD_RGHT-1] = EmptySquare;
9397     } else if (board[fromY][fromX] == king
9398         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9399                && toY == fromY && toX < fromX-1) {
9400         board[fromY][fromX] = EmptySquare;
9401         board[toY][toX] = king;
9402         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9403         board[fromY][BOARD_LEFT] = EmptySquare;
9404     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9405                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9406                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9407                ) {
9408         /* white pawn promotion */
9409         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9410         if(gameInfo.variant==VariantBughouse ||
9411            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9412             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9413         board[fromY][fromX] = EmptySquare;
9414     } else if ((fromY >= BOARD_HEIGHT>>1)
9415                && (toX != fromX)
9416                && gameInfo.variant != VariantXiangqi
9417                && gameInfo.variant != VariantBerolina
9418                && (board[fromY][fromX] == WhitePawn)
9419                && (board[toY][toX] == EmptySquare)) {
9420         board[fromY][fromX] = EmptySquare;
9421         board[toY][toX] = WhitePawn;
9422         captured = board[toY - 1][toX];
9423         board[toY - 1][toX] = EmptySquare;
9424     } else if ((fromY == BOARD_HEIGHT-4)
9425                && (toX == fromX)
9426                && gameInfo.variant == VariantBerolina
9427                && (board[fromY][fromX] == WhitePawn)
9428                && (board[toY][toX] == EmptySquare)) {
9429         board[fromY][fromX] = EmptySquare;
9430         board[toY][toX] = WhitePawn;
9431         if(oldEP & EP_BEROLIN_A) {
9432                 captured = board[fromY][fromX-1];
9433                 board[fromY][fromX-1] = EmptySquare;
9434         }else{  captured = board[fromY][fromX+1];
9435                 board[fromY][fromX+1] = EmptySquare;
9436         }
9437     } else if (board[fromY][fromX] == king
9438         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9439                && toY == fromY && toX > fromX+1) {
9440         board[fromY][fromX] = EmptySquare;
9441         board[toY][toX] = king;
9442         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9443         board[fromY][BOARD_RGHT-1] = EmptySquare;
9444     } else if (board[fromY][fromX] == king
9445         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9446                && toY == fromY && toX < fromX-1) {
9447         board[fromY][fromX] = EmptySquare;
9448         board[toY][toX] = king;
9449         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9450         board[fromY][BOARD_LEFT] = EmptySquare;
9451     } else if (fromY == 7 && fromX == 3
9452                && board[fromY][fromX] == BlackKing
9453                && toY == 7 && toX == 5) {
9454         board[fromY][fromX] = EmptySquare;
9455         board[toY][toX] = BlackKing;
9456         board[fromY][7] = EmptySquare;
9457         board[toY][4] = BlackRook;
9458     } else if (fromY == 7 && fromX == 3
9459                && board[fromY][fromX] == BlackKing
9460                && toY == 7 && toX == 1) {
9461         board[fromY][fromX] = EmptySquare;
9462         board[toY][toX] = BlackKing;
9463         board[fromY][0] = EmptySquare;
9464         board[toY][2] = BlackRook;
9465     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9466                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9467                && toY < promoRank && promoChar
9468                ) {
9469         /* black pawn promotion */
9470         board[toY][toX] = CharToPiece(ToLower(promoChar));
9471         if(gameInfo.variant==VariantBughouse ||
9472            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9473             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9474         board[fromY][fromX] = EmptySquare;
9475     } else if ((fromY < BOARD_HEIGHT>>1)
9476                && (toX != fromX)
9477                && gameInfo.variant != VariantXiangqi
9478                && gameInfo.variant != VariantBerolina
9479                && (board[fromY][fromX] == BlackPawn)
9480                && (board[toY][toX] == EmptySquare)) {
9481         board[fromY][fromX] = EmptySquare;
9482         board[toY][toX] = BlackPawn;
9483         captured = board[toY + 1][toX];
9484         board[toY + 1][toX] = EmptySquare;
9485     } else if ((fromY == 3)
9486                && (toX == fromX)
9487                && gameInfo.variant == VariantBerolina
9488                && (board[fromY][fromX] == BlackPawn)
9489                && (board[toY][toX] == EmptySquare)) {
9490         board[fromY][fromX] = EmptySquare;
9491         board[toY][toX] = BlackPawn;
9492         if(oldEP & EP_BEROLIN_A) {
9493                 captured = board[fromY][fromX-1];
9494                 board[fromY][fromX-1] = EmptySquare;
9495         }else{  captured = board[fromY][fromX+1];
9496                 board[fromY][fromX+1] = EmptySquare;
9497         }
9498     } else {
9499         board[toY][toX] = board[fromY][fromX];
9500         board[fromY][fromX] = EmptySquare;
9501     }
9502   }
9503
9504     if (gameInfo.holdingsWidth != 0) {
9505
9506       /* !!A lot more code needs to be written to support holdings  */
9507       /* [HGM] OK, so I have written it. Holdings are stored in the */
9508       /* penultimate board files, so they are automaticlly stored   */
9509       /* in the game history.                                       */
9510       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9511                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9512         /* Delete from holdings, by decreasing count */
9513         /* and erasing image if necessary            */
9514         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9515         if(p < (int) BlackPawn) { /* white drop */
9516              p -= (int)WhitePawn;
9517                  p = PieceToNumber((ChessSquare)p);
9518              if(p >= gameInfo.holdingsSize) p = 0;
9519              if(--board[p][BOARD_WIDTH-2] <= 0)
9520                   board[p][BOARD_WIDTH-1] = EmptySquare;
9521              if((int)board[p][BOARD_WIDTH-2] < 0)
9522                         board[p][BOARD_WIDTH-2] = 0;
9523         } else {                  /* black drop */
9524              p -= (int)BlackPawn;
9525                  p = PieceToNumber((ChessSquare)p);
9526              if(p >= gameInfo.holdingsSize) p = 0;
9527              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9528                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9529              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9530                         board[BOARD_HEIGHT-1-p][1] = 0;
9531         }
9532       }
9533       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9534           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9535         /* [HGM] holdings: Add to holdings, if holdings exist */
9536         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9537                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9538                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9539         }
9540         p = (int) captured;
9541         if (p >= (int) BlackPawn) {
9542           p -= (int)BlackPawn;
9543           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9544                   /* in Shogi restore piece to its original  first */
9545                   captured = (ChessSquare) (DEMOTED captured);
9546                   p = DEMOTED p;
9547           }
9548           p = PieceToNumber((ChessSquare)p);
9549           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9550           board[p][BOARD_WIDTH-2]++;
9551           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9552         } else {
9553           p -= (int)WhitePawn;
9554           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9555                   captured = (ChessSquare) (DEMOTED captured);
9556                   p = DEMOTED p;
9557           }
9558           p = PieceToNumber((ChessSquare)p);
9559           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9560           board[BOARD_HEIGHT-1-p][1]++;
9561           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9562         }
9563       }
9564     } else if (gameInfo.variant == VariantAtomic) {
9565       if (captured != EmptySquare) {
9566         int y, x;
9567         for (y = toY-1; y <= toY+1; y++) {
9568           for (x = toX-1; x <= toX+1; x++) {
9569             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9570                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9571               board[y][x] = EmptySquare;
9572             }
9573           }
9574         }
9575         board[toY][toX] = EmptySquare;
9576       }
9577     }
9578     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9579         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9580     } else
9581     if(promoChar == '+') {
9582         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9583         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9584     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9585         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9586         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9587            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9588         board[toY][toX] = newPiece;
9589     }
9590     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9591                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9592         // [HGM] superchess: take promotion piece out of holdings
9593         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9594         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9595             if(!--board[k][BOARD_WIDTH-2])
9596                 board[k][BOARD_WIDTH-1] = EmptySquare;
9597         } else {
9598             if(!--board[BOARD_HEIGHT-1-k][1])
9599                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9600         }
9601     }
9602
9603 }
9604
9605 /* Updates forwardMostMove */
9606 void
9607 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9608 {
9609 //    forwardMostMove++; // [HGM] bare: moved downstream
9610
9611     (void) CoordsToAlgebraic(boards[forwardMostMove],
9612                              PosFlags(forwardMostMove),
9613                              fromY, fromX, toY, toX, promoChar,
9614                              parseList[forwardMostMove]);
9615
9616     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9617         int timeLeft; static int lastLoadFlag=0; int king, piece;
9618         piece = boards[forwardMostMove][fromY][fromX];
9619         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9620         if(gameInfo.variant == VariantKnightmate)
9621             king += (int) WhiteUnicorn - (int) WhiteKing;
9622         if(forwardMostMove == 0) {
9623             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9624                 fprintf(serverMoves, "%s;", UserName());
9625             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9626                 fprintf(serverMoves, "%s;", second.tidy);
9627             fprintf(serverMoves, "%s;", first.tidy);
9628             if(gameMode == MachinePlaysWhite)
9629                 fprintf(serverMoves, "%s;", UserName());
9630             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9631                 fprintf(serverMoves, "%s;", second.tidy);
9632         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9633         lastLoadFlag = loadFlag;
9634         // print base move
9635         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9636         // print castling suffix
9637         if( toY == fromY && piece == king ) {
9638             if(toX-fromX > 1)
9639                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9640             if(fromX-toX >1)
9641                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9642         }
9643         // e.p. suffix
9644         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9645              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9646              boards[forwardMostMove][toY][toX] == EmptySquare
9647              && fromX != toX && fromY != toY)
9648                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9649         // promotion suffix
9650         if(promoChar != NULLCHAR) {
9651             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9652                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9653                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9654             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9655         }
9656         if(!loadFlag) {
9657                 char buf[MOVE_LEN*2], *p; int len;
9658             fprintf(serverMoves, "/%d/%d",
9659                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9660             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9661             else                      timeLeft = blackTimeRemaining/1000;
9662             fprintf(serverMoves, "/%d", timeLeft);
9663                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9664                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9665                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9666                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9667             fprintf(serverMoves, "/%s", buf);
9668         }
9669         fflush(serverMoves);
9670     }
9671
9672     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9673         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9674       return;
9675     }
9676     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9677     if (commentList[forwardMostMove+1] != NULL) {
9678         free(commentList[forwardMostMove+1]);
9679         commentList[forwardMostMove+1] = NULL;
9680     }
9681     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9682     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9683     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9684     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9685     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9686     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9687     adjustedClock = FALSE;
9688     gameInfo.result = GameUnfinished;
9689     if (gameInfo.resultDetails != NULL) {
9690         free(gameInfo.resultDetails);
9691         gameInfo.resultDetails = NULL;
9692     }
9693     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9694                               moveList[forwardMostMove - 1]);
9695     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9696       case MT_NONE:
9697       case MT_STALEMATE:
9698       default:
9699         break;
9700       case MT_CHECK:
9701         if(gameInfo.variant != VariantShogi)
9702             strcat(parseList[forwardMostMove - 1], "+");
9703         break;
9704       case MT_CHECKMATE:
9705       case MT_STAINMATE:
9706         strcat(parseList[forwardMostMove - 1], "#");
9707         break;
9708     }
9709
9710 }
9711
9712 /* Updates currentMove if not pausing */
9713 void
9714 ShowMove (int fromX, int fromY, int toX, int toY)
9715 {
9716     int instant = (gameMode == PlayFromGameFile) ?
9717         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9718     if(appData.noGUI) return;
9719     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9720         if (!instant) {
9721             if (forwardMostMove == currentMove + 1) {
9722                 AnimateMove(boards[forwardMostMove - 1],
9723                             fromX, fromY, toX, toY);
9724             }
9725         }
9726         currentMove = forwardMostMove;
9727     }
9728
9729     if (instant) return;
9730
9731     DisplayMove(currentMove - 1);
9732     DrawPosition(FALSE, boards[currentMove]);
9733     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9734             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9735                 SetHighlights(fromX, fromY, toX, toY);
9736             }
9737     }
9738     DisplayBothClocks();
9739     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9740 }
9741
9742 void
9743 SendEgtPath (ChessProgramState *cps)
9744 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9745         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9746
9747         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9748
9749         while(*p) {
9750             char c, *q = name+1, *r, *s;
9751
9752             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9753             while(*p && *p != ',') *q++ = *p++;
9754             *q++ = ':'; *q = 0;
9755             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9756                 strcmp(name, ",nalimov:") == 0 ) {
9757                 // take nalimov path from the menu-changeable option first, if it is defined
9758               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9759                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9760             } else
9761             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9762                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9763                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9764                 s = r = StrStr(s, ":") + 1; // beginning of path info
9765                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9766                 c = *r; *r = 0;             // temporarily null-terminate path info
9767                     *--q = 0;               // strip of trailig ':' from name
9768                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9769                 *r = c;
9770                 SendToProgram(buf,cps);     // send egtbpath command for this format
9771             }
9772             if(*p == ',') p++; // read away comma to position for next format name
9773         }
9774 }
9775
9776 void
9777 InitChessProgram (ChessProgramState *cps, int setup)
9778 /* setup needed to setup FRC opening position */
9779 {
9780     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9781     if (appData.noChessProgram) return;
9782     hintRequested = FALSE;
9783     bookRequested = FALSE;
9784
9785     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9786     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9787     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9788     if(cps->memSize) { /* [HGM] memory */
9789       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9790         SendToProgram(buf, cps);
9791     }
9792     SendEgtPath(cps); /* [HGM] EGT */
9793     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9794       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9795         SendToProgram(buf, cps);
9796     }
9797
9798     SendToProgram(cps->initString, cps);
9799     if (gameInfo.variant != VariantNormal &&
9800         gameInfo.variant != VariantLoadable
9801         /* [HGM] also send variant if board size non-standard */
9802         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9803                                             ) {
9804       char *v = VariantName(gameInfo.variant);
9805       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9806         /* [HGM] in protocol 1 we have to assume all variants valid */
9807         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9808         DisplayFatalError(buf, 0, 1);
9809         return;
9810       }
9811
9812       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9813       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9814       if( gameInfo.variant == VariantXiangqi )
9815            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9816       if( gameInfo.variant == VariantShogi )
9817            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9818       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9819            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9820       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9821           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9822            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9823       if( gameInfo.variant == VariantCourier )
9824            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9825       if( gameInfo.variant == VariantSuper )
9826            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9827       if( gameInfo.variant == VariantGreat )
9828            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9829       if( gameInfo.variant == VariantSChess )
9830            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9831       if( gameInfo.variant == VariantGrand )
9832            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9833
9834       if(overruled) {
9835         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9836                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9837            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9838            if(StrStr(cps->variants, b) == NULL) {
9839                // specific sized variant not known, check if general sizing allowed
9840                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9841                    if(StrStr(cps->variants, "boardsize") == NULL) {
9842                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9843                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9844                        DisplayFatalError(buf, 0, 1);
9845                        return;
9846                    }
9847                    /* [HGM] here we really should compare with the maximum supported board size */
9848                }
9849            }
9850       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9851       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9852       SendToProgram(buf, cps);
9853     }
9854     currentlyInitializedVariant = gameInfo.variant;
9855
9856     /* [HGM] send opening position in FRC to first engine */
9857     if(setup) {
9858           SendToProgram("force\n", cps);
9859           SendBoard(cps, 0);
9860           /* engine is now in force mode! Set flag to wake it up after first move. */
9861           setboardSpoiledMachineBlack = 1;
9862     }
9863
9864     if (cps->sendICS) {
9865       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9866       SendToProgram(buf, cps);
9867     }
9868     cps->maybeThinking = FALSE;
9869     cps->offeredDraw = 0;
9870     if (!appData.icsActive) {
9871         SendTimeControl(cps, movesPerSession, timeControl,
9872                         timeIncrement, appData.searchDepth,
9873                         searchTime);
9874     }
9875     if (appData.showThinking
9876         // [HGM] thinking: four options require thinking output to be sent
9877         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9878                                 ) {
9879         SendToProgram("post\n", cps);
9880     }
9881     SendToProgram("hard\n", cps);
9882     if (!appData.ponderNextMove) {
9883         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9884            it without being sure what state we are in first.  "hard"
9885            is not a toggle, so that one is OK.
9886          */
9887         SendToProgram("easy\n", cps);
9888     }
9889     if (cps->usePing) {
9890       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9891       SendToProgram(buf, cps);
9892     }
9893     cps->initDone = TRUE;
9894     ClearEngineOutputPane(cps == &second);
9895 }
9896
9897
9898 void
9899 StartChessProgram (ChessProgramState *cps)
9900 {
9901     char buf[MSG_SIZ];
9902     int err;
9903
9904     if (appData.noChessProgram) return;
9905     cps->initDone = FALSE;
9906
9907     if (strcmp(cps->host, "localhost") == 0) {
9908         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9909     } else if (*appData.remoteShell == NULLCHAR) {
9910         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9911     } else {
9912         if (*appData.remoteUser == NULLCHAR) {
9913           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9914                     cps->program);
9915         } else {
9916           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9917                     cps->host, appData.remoteUser, cps->program);
9918         }
9919         err = StartChildProcess(buf, "", &cps->pr);
9920     }
9921
9922     if (err != 0) {
9923       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9924         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9925         if(cps != &first) return;
9926         appData.noChessProgram = TRUE;
9927         ThawUI();
9928         SetNCPMode();
9929 //      DisplayFatalError(buf, err, 1);
9930 //      cps->pr = NoProc;
9931 //      cps->isr = NULL;
9932         return;
9933     }
9934
9935     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9936     if (cps->protocolVersion > 1) {
9937       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9938       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9939       cps->comboCnt = 0;  //                and values of combo boxes
9940       SendToProgram(buf, cps);
9941     } else {
9942       SendToProgram("xboard\n", cps);
9943     }
9944 }
9945
9946 void
9947 TwoMachinesEventIfReady P((void))
9948 {
9949   static int curMess = 0;
9950   if (first.lastPing != first.lastPong) {
9951     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9952     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9953     return;
9954   }
9955   if (second.lastPing != second.lastPong) {
9956     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9957     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9958     return;
9959   }
9960   DisplayMessage("", ""); curMess = 0;
9961   ThawUI();
9962   TwoMachinesEvent();
9963 }
9964
9965 char *
9966 MakeName (char *template)
9967 {
9968     time_t clock;
9969     struct tm *tm;
9970     static char buf[MSG_SIZ];
9971     char *p = buf;
9972     int i;
9973
9974     clock = time((time_t *)NULL);
9975     tm = localtime(&clock);
9976
9977     while(*p++ = *template++) if(p[-1] == '%') {
9978         switch(*template++) {
9979           case 0:   *p = 0; return buf;
9980           case 'Y': i = tm->tm_year+1900; break;
9981           case 'y': i = tm->tm_year-100; break;
9982           case 'M': i = tm->tm_mon+1; break;
9983           case 'd': i = tm->tm_mday; break;
9984           case 'h': i = tm->tm_hour; break;
9985           case 'm': i = tm->tm_min; break;
9986           case 's': i = tm->tm_sec; break;
9987           default:  i = 0;
9988         }
9989         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9990     }
9991     return buf;
9992 }
9993
9994 int
9995 CountPlayers (char *p)
9996 {
9997     int n = 0;
9998     while(p = strchr(p, '\n')) p++, n++; // count participants
9999     return n;
10000 }
10001
10002 FILE *
10003 WriteTourneyFile (char *results, FILE *f)
10004 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10005     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10006     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10007         // create a file with tournament description
10008         fprintf(f, "-participants {%s}\n", appData.participants);
10009         fprintf(f, "-seedBase %d\n", appData.seedBase);
10010         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10011         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10012         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10013         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10014         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10015         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10016         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10017         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10018         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10019         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10020         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10021         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10022         if(searchTime > 0)
10023                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10024         else {
10025                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10026                 fprintf(f, "-tc %s\n", appData.timeControl);
10027                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10028         }
10029         fprintf(f, "-results \"%s\"\n", results);
10030     }
10031     return f;
10032 }
10033
10034 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10035
10036 void
10037 Substitute (char *participants, int expunge)
10038 {
10039     int i, changed, changes=0, nPlayers=0;
10040     char *p, *q, *r, buf[MSG_SIZ];
10041     if(participants == NULL) return;
10042     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10043     r = p = participants; q = appData.participants;
10044     while(*p && *p == *q) {
10045         if(*p == '\n') r = p+1, nPlayers++;
10046         p++; q++;
10047     }
10048     if(*p) { // difference
10049         while(*p && *p++ != '\n');
10050         while(*q && *q++ != '\n');
10051       changed = nPlayers;
10052         changes = 1 + (strcmp(p, q) != 0);
10053     }
10054     if(changes == 1) { // a single engine mnemonic was changed
10055         q = r; while(*q) nPlayers += (*q++ == '\n');
10056         p = buf; while(*r && (*p = *r++) != '\n') p++;
10057         *p = NULLCHAR;
10058         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10059         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10060         if(mnemonic[i]) { // The substitute is valid
10061             FILE *f;
10062             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10063                 flock(fileno(f), LOCK_EX);
10064                 ParseArgsFromFile(f);
10065                 fseek(f, 0, SEEK_SET);
10066                 FREE(appData.participants); appData.participants = participants;
10067                 if(expunge) { // erase results of replaced engine
10068                     int len = strlen(appData.results), w, b, dummy;
10069                     for(i=0; i<len; i++) {
10070                         Pairing(i, nPlayers, &w, &b, &dummy);
10071                         if((w == changed || b == changed) && appData.results[i] == '*') {
10072                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10073                             fclose(f);
10074                             return;
10075                         }
10076                     }
10077                     for(i=0; i<len; i++) {
10078                         Pairing(i, nPlayers, &w, &b, &dummy);
10079                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10080                     }
10081                 }
10082                 WriteTourneyFile(appData.results, f);
10083                 fclose(f); // release lock
10084                 return;
10085             }
10086         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10087     }
10088     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10089     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10090     free(participants);
10091     return;
10092 }
10093
10094 int
10095 CheckPlayers (char *participants)
10096 {
10097         int i;
10098         char buf[MSG_SIZ], *p;
10099         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10100         while(p = strchr(participants, '\n')) {
10101             *p = NULLCHAR;
10102             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10103             if(!mnemonic[i]) {
10104                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10105                 *p = '\n';
10106                 DisplayError(buf, 0);
10107                 return 1;
10108             }
10109             *p = '\n';
10110             participants = p + 1;
10111         }
10112         return 0;
10113 }
10114
10115 int
10116 CreateTourney (char *name)
10117 {
10118         FILE *f;
10119         if(matchMode && strcmp(name, appData.tourneyFile)) {
10120              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10121         }
10122         if(name[0] == NULLCHAR) {
10123             if(appData.participants[0])
10124                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10125             return 0;
10126         }
10127         f = fopen(name, "r");
10128         if(f) { // file exists
10129             ASSIGN(appData.tourneyFile, name);
10130             ParseArgsFromFile(f); // parse it
10131         } else {
10132             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10133             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10134                 DisplayError(_("Not enough participants"), 0);
10135                 return 0;
10136             }
10137             if(CheckPlayers(appData.participants)) return 0;
10138             ASSIGN(appData.tourneyFile, name);
10139             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10140             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10141         }
10142         fclose(f);
10143         appData.noChessProgram = FALSE;
10144         appData.clockMode = TRUE;
10145         SetGNUMode();
10146         return 1;
10147 }
10148
10149 int
10150 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10151 {
10152     char buf[MSG_SIZ], *p, *q;
10153     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10154     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10155     skip = !all && group[0]; // if group requested, we start in skip mode
10156     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10157         p = names; q = buf; header = 0;
10158         while(*p && *p != '\n') *q++ = *p++;
10159         *q = 0;
10160         if(*p == '\n') p++;
10161         if(buf[0] == '#') {
10162             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10163             depth++; // we must be entering a new group
10164             if(all) continue; // suppress printing group headers when complete list requested
10165             header = 1;
10166             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10167         }
10168         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10169         if(engineList[i]) free(engineList[i]);
10170         engineList[i] = strdup(buf);
10171         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10172         if(engineMnemonic[i]) free(engineMnemonic[i]);
10173         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10174             strcat(buf, " (");
10175             sscanf(q + 8, "%s", buf + strlen(buf));
10176             strcat(buf, ")");
10177         }
10178         engineMnemonic[i] = strdup(buf);
10179         i++;
10180     }
10181     engineList[i] = engineMnemonic[i] = NULL;
10182     return i;
10183 }
10184
10185 // following implemented as macro to avoid type limitations
10186 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10187
10188 void
10189 SwapEngines (int n)
10190 {   // swap settings for first engine and other engine (so far only some selected options)
10191     int h;
10192     char *p;
10193     if(n == 0) return;
10194     SWAP(directory, p)
10195     SWAP(chessProgram, p)
10196     SWAP(isUCI, h)
10197     SWAP(hasOwnBookUCI, h)
10198     SWAP(protocolVersion, h)
10199     SWAP(reuse, h)
10200     SWAP(scoreIsAbsolute, h)
10201     SWAP(timeOdds, h)
10202     SWAP(logo, p)
10203     SWAP(pgnName, p)
10204     SWAP(pvSAN, h)
10205     SWAP(engOptions, p)
10206     SWAP(engInitString, p)
10207     SWAP(computerString, p)
10208     SWAP(features, p)
10209     SWAP(fenOverride, p)
10210     SWAP(NPS, h)
10211     SWAP(accumulateTC, h)
10212     SWAP(host, p)
10213 }
10214
10215 int
10216 GetEngineLine (char *s, int n)
10217 {
10218     int i;
10219     char buf[MSG_SIZ];
10220     extern char *icsNames;
10221     if(!s || !*s) return 0;
10222     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10223     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10224     if(!mnemonic[i]) return 0;
10225     if(n == 11) return 1; // just testing if there was a match
10226     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10227     if(n == 1) SwapEngines(n);
10228     ParseArgsFromString(buf);
10229     if(n == 1) SwapEngines(n);
10230     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10231         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10232         ParseArgsFromString(buf);
10233     }
10234     return 1;
10235 }
10236
10237 int
10238 SetPlayer (int player, char *p)
10239 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10240     int i;
10241     char buf[MSG_SIZ], *engineName;
10242     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10243     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10244     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10245     if(mnemonic[i]) {
10246         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10247         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10248         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10249         ParseArgsFromString(buf);
10250     }
10251     free(engineName);
10252     return i;
10253 }
10254
10255 char *recentEngines;
10256
10257 void
10258 RecentEngineEvent (int nr)
10259 {
10260     int n;
10261 //    SwapEngines(1); // bump first to second
10262 //    ReplaceEngine(&second, 1); // and load it there
10263     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10264     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10265     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10266         ReplaceEngine(&first, 0);
10267         FloatToFront(&appData.recentEngineList, command[n]);
10268     }
10269 }
10270
10271 int
10272 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10273 {   // determine players from game number
10274     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10275
10276     if(appData.tourneyType == 0) {
10277         roundsPerCycle = (nPlayers - 1) | 1;
10278         pairingsPerRound = nPlayers / 2;
10279     } else if(appData.tourneyType > 0) {
10280         roundsPerCycle = nPlayers - appData.tourneyType;
10281         pairingsPerRound = appData.tourneyType;
10282     }
10283     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10284     gamesPerCycle = gamesPerRound * roundsPerCycle;
10285     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10286     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10287     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10288     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10289     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10290     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10291
10292     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10293     if(appData.roundSync) *syncInterval = gamesPerRound;
10294
10295     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10296
10297     if(appData.tourneyType == 0) {
10298         if(curPairing == (nPlayers-1)/2 ) {
10299             *whitePlayer = curRound;
10300             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10301         } else {
10302             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10303             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10304             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10305             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10306         }
10307     } else if(appData.tourneyType > 1) {
10308         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10309         *whitePlayer = curRound + appData.tourneyType;
10310     } else if(appData.tourneyType > 0) {
10311         *whitePlayer = curPairing;
10312         *blackPlayer = curRound + appData.tourneyType;
10313     }
10314
10315     // take care of white/black alternation per round. 
10316     // For cycles and games this is already taken care of by default, derived from matchGame!
10317     return curRound & 1;
10318 }
10319
10320 int
10321 NextTourneyGame (int nr, int *swapColors)
10322 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10323     char *p, *q;
10324     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10325     FILE *tf;
10326     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10327     tf = fopen(appData.tourneyFile, "r");
10328     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10329     ParseArgsFromFile(tf); fclose(tf);
10330     InitTimeControls(); // TC might be altered from tourney file
10331
10332     nPlayers = CountPlayers(appData.participants); // count participants
10333     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10334     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10335
10336     if(syncInterval) {
10337         p = q = appData.results;
10338         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10339         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10340             DisplayMessage(_("Waiting for other game(s)"),"");
10341             waitingForGame = TRUE;
10342             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10343             return 0;
10344         }
10345         waitingForGame = FALSE;
10346     }
10347
10348     if(appData.tourneyType < 0) {
10349         if(nr>=0 && !pairingReceived) {
10350             char buf[1<<16];
10351             if(pairing.pr == NoProc) {
10352                 if(!appData.pairingEngine[0]) {
10353                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10354                     return 0;
10355                 }
10356                 StartChessProgram(&pairing); // starts the pairing engine
10357             }
10358             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10359             SendToProgram(buf, &pairing);
10360             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10361             SendToProgram(buf, &pairing);
10362             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10363         }
10364         pairingReceived = 0;                              // ... so we continue here 
10365         *swapColors = 0;
10366         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10367         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10368         matchGame = 1; roundNr = nr / syncInterval + 1;
10369     }
10370
10371     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10372
10373     // redefine engines, engine dir, etc.
10374     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10375     if(first.pr == NoProc) {
10376       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10377       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10378     }
10379     if(second.pr == NoProc) {
10380       SwapEngines(1);
10381       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10382       SwapEngines(1);         // and make that valid for second engine by swapping
10383       InitEngine(&second, 1);
10384     }
10385     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10386     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10387     return 1;
10388 }
10389
10390 void
10391 NextMatchGame ()
10392 {   // performs game initialization that does not invoke engines, and then tries to start the game
10393     int res, firstWhite, swapColors = 0;
10394     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10395     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
10396         char buf[MSG_SIZ];
10397         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10398         if(strcmp(buf, currentDebugFile)) { // name has changed
10399             FILE *f = fopen(buf, "w");
10400             if(f) { // if opening the new file failed, just keep using the old one
10401                 ASSIGN(currentDebugFile, buf);
10402                 fclose(debugFP);
10403                 debugFP = f;
10404             }
10405             if(appData.serverFileName) {
10406                 if(serverFP) fclose(serverFP);
10407                 serverFP = fopen(appData.serverFileName, "w");
10408                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10409                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10410             }
10411         }
10412     }
10413     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10414     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10415     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10416     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10417     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10418     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10419     Reset(FALSE, first.pr != NoProc);
10420     res = LoadGameOrPosition(matchGame); // setup game
10421     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10422     if(!res) return; // abort when bad game/pos file
10423     TwoMachinesEvent();
10424 }
10425
10426 void
10427 UserAdjudicationEvent (int result)
10428 {
10429     ChessMove gameResult = GameIsDrawn;
10430
10431     if( result > 0 ) {
10432         gameResult = WhiteWins;
10433     }
10434     else if( result < 0 ) {
10435         gameResult = BlackWins;
10436     }
10437
10438     if( gameMode == TwoMachinesPlay ) {
10439         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10440     }
10441 }
10442
10443
10444 // [HGM] save: calculate checksum of game to make games easily identifiable
10445 int
10446 StringCheckSum (char *s)
10447 {
10448         int i = 0;
10449         if(s==NULL) return 0;
10450         while(*s) i = i*259 + *s++;
10451         return i;
10452 }
10453
10454 int
10455 GameCheckSum ()
10456 {
10457         int i, sum=0;
10458         for(i=backwardMostMove; i<forwardMostMove; i++) {
10459                 sum += pvInfoList[i].depth;
10460                 sum += StringCheckSum(parseList[i]);
10461                 sum += StringCheckSum(commentList[i]);
10462                 sum *= 261;
10463         }
10464         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10465         return sum + StringCheckSum(commentList[i]);
10466 } // end of save patch
10467
10468 void
10469 GameEnds (ChessMove result, char *resultDetails, int whosays)
10470 {
10471     GameMode nextGameMode;
10472     int isIcsGame;
10473     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10474
10475     if(endingGame) return; /* [HGM] crash: forbid recursion */
10476     endingGame = 1;
10477     if(twoBoards) { // [HGM] dual: switch back to one board
10478         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10479         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10480     }
10481     if (appData.debugMode) {
10482       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10483               result, resultDetails ? resultDetails : "(null)", whosays);
10484     }
10485
10486     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10487
10488     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10489         /* If we are playing on ICS, the server decides when the
10490            game is over, but the engine can offer to draw, claim
10491            a draw, or resign.
10492          */
10493 #if ZIPPY
10494         if (appData.zippyPlay && first.initDone) {
10495             if (result == GameIsDrawn) {
10496                 /* In case draw still needs to be claimed */
10497                 SendToICS(ics_prefix);
10498                 SendToICS("draw\n");
10499             } else if (StrCaseStr(resultDetails, "resign")) {
10500                 SendToICS(ics_prefix);
10501                 SendToICS("resign\n");
10502             }
10503         }
10504 #endif
10505         endingGame = 0; /* [HGM] crash */
10506         return;
10507     }
10508
10509     /* If we're loading the game from a file, stop */
10510     if (whosays == GE_FILE) {
10511       (void) StopLoadGameTimer();
10512       gameFileFP = NULL;
10513     }
10514
10515     /* Cancel draw offers */
10516     first.offeredDraw = second.offeredDraw = 0;
10517
10518     /* If this is an ICS game, only ICS can really say it's done;
10519        if not, anyone can. */
10520     isIcsGame = (gameMode == IcsPlayingWhite ||
10521                  gameMode == IcsPlayingBlack ||
10522                  gameMode == IcsObserving    ||
10523                  gameMode == IcsExamining);
10524
10525     if (!isIcsGame || whosays == GE_ICS) {
10526         /* OK -- not an ICS game, or ICS said it was done */
10527         StopClocks();
10528         if (!isIcsGame && !appData.noChessProgram)
10529           SetUserThinkingEnables();
10530
10531         /* [HGM] if a machine claims the game end we verify this claim */
10532         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10533             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10534                 char claimer;
10535                 ChessMove trueResult = (ChessMove) -1;
10536
10537                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10538                                             first.twoMachinesColor[0] :
10539                                             second.twoMachinesColor[0] ;
10540
10541                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10542                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10543                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10544                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10545                 } else
10546                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10547                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10548                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10549                 } else
10550                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10551                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10552                 }
10553
10554                 // now verify win claims, but not in drop games, as we don't understand those yet
10555                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10556                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10557                     (result == WhiteWins && claimer == 'w' ||
10558                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10559                       if (appData.debugMode) {
10560                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10561                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10562                       }
10563                       if(result != trueResult) {
10564                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10565                               result = claimer == 'w' ? BlackWins : WhiteWins;
10566                               resultDetails = buf;
10567                       }
10568                 } else
10569                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10570                     && (forwardMostMove <= backwardMostMove ||
10571                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10572                         (claimer=='b')==(forwardMostMove&1))
10573                                                                                   ) {
10574                       /* [HGM] verify: draws that were not flagged are false claims */
10575                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10576                       result = claimer == 'w' ? BlackWins : WhiteWins;
10577                       resultDetails = buf;
10578                 }
10579                 /* (Claiming a loss is accepted no questions asked!) */
10580             }
10581             /* [HGM] bare: don't allow bare King to win */
10582             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10583                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10584                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10585                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10586                && result != GameIsDrawn)
10587             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10588                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10589                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10590                         if(p >= 0 && p <= (int)WhiteKing) k++;
10591                 }
10592                 if (appData.debugMode) {
10593                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10594                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10595                 }
10596                 if(k <= 1) {
10597                         result = GameIsDrawn;
10598                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10599                         resultDetails = buf;
10600                 }
10601             }
10602         }
10603
10604
10605         if(serverMoves != NULL && !loadFlag) { char c = '=';
10606             if(result==WhiteWins) c = '+';
10607             if(result==BlackWins) c = '-';
10608             if(resultDetails != NULL)
10609                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10610         }
10611         if (resultDetails != NULL) {
10612             gameInfo.result = result;
10613             gameInfo.resultDetails = StrSave(resultDetails);
10614
10615             /* display last move only if game was not loaded from file */
10616             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10617                 DisplayMove(currentMove - 1);
10618
10619             if (forwardMostMove != 0) {
10620                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10621                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10622                                                                 ) {
10623                     if (*appData.saveGameFile != NULLCHAR) {
10624                         SaveGameToFile(appData.saveGameFile, TRUE);
10625                     } else if (appData.autoSaveGames) {
10626                         AutoSaveGame();
10627                     }
10628                     if (*appData.savePositionFile != NULLCHAR) {
10629                         SavePositionToFile(appData.savePositionFile);
10630                     }
10631                 }
10632             }
10633
10634             /* Tell program how game ended in case it is learning */
10635             /* [HGM] Moved this to after saving the PGN, just in case */
10636             /* engine died and we got here through time loss. In that */
10637             /* case we will get a fatal error writing the pipe, which */
10638             /* would otherwise lose us the PGN.                       */
10639             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10640             /* output during GameEnds should never be fatal anymore   */
10641             if (gameMode == MachinePlaysWhite ||
10642                 gameMode == MachinePlaysBlack ||
10643                 gameMode == TwoMachinesPlay ||
10644                 gameMode == IcsPlayingWhite ||
10645                 gameMode == IcsPlayingBlack ||
10646                 gameMode == BeginningOfGame) {
10647                 char buf[MSG_SIZ];
10648                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10649                         resultDetails);
10650                 if (first.pr != NoProc) {
10651                     SendToProgram(buf, &first);
10652                 }
10653                 if (second.pr != NoProc &&
10654                     gameMode == TwoMachinesPlay) {
10655                     SendToProgram(buf, &second);
10656                 }
10657             }
10658         }
10659
10660         if (appData.icsActive) {
10661             if (appData.quietPlay &&
10662                 (gameMode == IcsPlayingWhite ||
10663                  gameMode == IcsPlayingBlack)) {
10664                 SendToICS(ics_prefix);
10665                 SendToICS("set shout 1\n");
10666             }
10667             nextGameMode = IcsIdle;
10668             ics_user_moved = FALSE;
10669             /* clean up premove.  It's ugly when the game has ended and the
10670              * premove highlights are still on the board.
10671              */
10672             if (gotPremove) {
10673               gotPremove = FALSE;
10674               ClearPremoveHighlights();
10675               DrawPosition(FALSE, boards[currentMove]);
10676             }
10677             if (whosays == GE_ICS) {
10678                 switch (result) {
10679                 case WhiteWins:
10680                     if (gameMode == IcsPlayingWhite)
10681                         PlayIcsWinSound();
10682                     else if(gameMode == IcsPlayingBlack)
10683                         PlayIcsLossSound();
10684                     break;
10685                 case BlackWins:
10686                     if (gameMode == IcsPlayingBlack)
10687                         PlayIcsWinSound();
10688                     else if(gameMode == IcsPlayingWhite)
10689                         PlayIcsLossSound();
10690                     break;
10691                 case GameIsDrawn:
10692                     PlayIcsDrawSound();
10693                     break;
10694                 default:
10695                     PlayIcsUnfinishedSound();
10696                 }
10697             }
10698         } else if (gameMode == EditGame ||
10699                    gameMode == PlayFromGameFile ||
10700                    gameMode == AnalyzeMode ||
10701                    gameMode == AnalyzeFile) {
10702             nextGameMode = gameMode;
10703         } else {
10704             nextGameMode = EndOfGame;
10705         }
10706         pausing = FALSE;
10707         ModeHighlight();
10708     } else {
10709         nextGameMode = gameMode;
10710     }
10711
10712     if (appData.noChessProgram) {
10713         gameMode = nextGameMode;
10714         ModeHighlight();
10715         endingGame = 0; /* [HGM] crash */
10716         return;
10717     }
10718
10719     if (first.reuse) {
10720         /* Put first chess program into idle state */
10721         if (first.pr != NoProc &&
10722             (gameMode == MachinePlaysWhite ||
10723              gameMode == MachinePlaysBlack ||
10724              gameMode == TwoMachinesPlay ||
10725              gameMode == IcsPlayingWhite ||
10726              gameMode == IcsPlayingBlack ||
10727              gameMode == BeginningOfGame)) {
10728             SendToProgram("force\n", &first);
10729             if (first.usePing) {
10730               char buf[MSG_SIZ];
10731               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10732               SendToProgram(buf, &first);
10733             }
10734         }
10735     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10736         /* Kill off first chess program */
10737         if (first.isr != NULL)
10738           RemoveInputSource(first.isr);
10739         first.isr = NULL;
10740
10741         if (first.pr != NoProc) {
10742             ExitAnalyzeMode();
10743             DoSleep( appData.delayBeforeQuit );
10744             SendToProgram("quit\n", &first);
10745             DoSleep( appData.delayAfterQuit );
10746             DestroyChildProcess(first.pr, first.useSigterm);
10747         }
10748         first.pr = NoProc;
10749     }
10750     if (second.reuse) {
10751         /* Put second chess program into idle state */
10752         if (second.pr != NoProc &&
10753             gameMode == TwoMachinesPlay) {
10754             SendToProgram("force\n", &second);
10755             if (second.usePing) {
10756               char buf[MSG_SIZ];
10757               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10758               SendToProgram(buf, &second);
10759             }
10760         }
10761     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10762         /* Kill off second chess program */
10763         if (second.isr != NULL)
10764           RemoveInputSource(second.isr);
10765         second.isr = NULL;
10766
10767         if (second.pr != NoProc) {
10768             DoSleep( appData.delayBeforeQuit );
10769             SendToProgram("quit\n", &second);
10770             DoSleep( appData.delayAfterQuit );
10771             DestroyChildProcess(second.pr, second.useSigterm);
10772         }
10773         second.pr = NoProc;
10774     }
10775
10776     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10777         char resChar = '=';
10778         switch (result) {
10779         case WhiteWins:
10780           resChar = '+';
10781           if (first.twoMachinesColor[0] == 'w') {
10782             first.matchWins++;
10783           } else {
10784             second.matchWins++;
10785           }
10786           break;
10787         case BlackWins:
10788           resChar = '-';
10789           if (first.twoMachinesColor[0] == 'b') {
10790             first.matchWins++;
10791           } else {
10792             second.matchWins++;
10793           }
10794           break;
10795         case GameUnfinished:
10796           resChar = ' ';
10797         default:
10798           break;
10799         }
10800
10801         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10802         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10803             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10804             ReserveGame(nextGame, resChar); // sets nextGame
10805             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10806             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10807         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10808
10809         if (nextGame <= appData.matchGames && !abortMatch) {
10810             gameMode = nextGameMode;
10811             matchGame = nextGame; // this will be overruled in tourney mode!
10812             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10813             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10814             endingGame = 0; /* [HGM] crash */
10815             return;
10816         } else {
10817             gameMode = nextGameMode;
10818             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10819                      first.tidy, second.tidy,
10820                      first.matchWins, second.matchWins,
10821                      appData.matchGames - (first.matchWins + second.matchWins));
10822             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10823             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10824             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10825             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10826                 first.twoMachinesColor = "black\n";
10827                 second.twoMachinesColor = "white\n";
10828             } else {
10829                 first.twoMachinesColor = "white\n";
10830                 second.twoMachinesColor = "black\n";
10831             }
10832         }
10833     }
10834     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10835         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10836       ExitAnalyzeMode();
10837     gameMode = nextGameMode;
10838     ModeHighlight();
10839     endingGame = 0;  /* [HGM] crash */
10840     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10841         if(matchMode == TRUE) { // match through command line: exit with or without popup
10842             if(ranking) {
10843                 ToNrEvent(forwardMostMove);
10844                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10845                 else ExitEvent(0);
10846             } else DisplayFatalError(buf, 0, 0);
10847         } else { // match through menu; just stop, with or without popup
10848             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10849             ModeHighlight();
10850             if(ranking){
10851                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10852             } else DisplayNote(buf);
10853       }
10854       if(ranking) free(ranking);
10855     }
10856 }
10857
10858 /* Assumes program was just initialized (initString sent).
10859    Leaves program in force mode. */
10860 void
10861 FeedMovesToProgram (ChessProgramState *cps, int upto)
10862 {
10863     int i;
10864
10865     if (appData.debugMode)
10866       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10867               startedFromSetupPosition ? "position and " : "",
10868               backwardMostMove, upto, cps->which);
10869     if(currentlyInitializedVariant != gameInfo.variant) {
10870       char buf[MSG_SIZ];
10871         // [HGM] variantswitch: make engine aware of new variant
10872         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10873                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10874         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10875         SendToProgram(buf, cps);
10876         currentlyInitializedVariant = gameInfo.variant;
10877     }
10878     SendToProgram("force\n", cps);
10879     if (startedFromSetupPosition) {
10880         SendBoard(cps, backwardMostMove);
10881     if (appData.debugMode) {
10882         fprintf(debugFP, "feedMoves\n");
10883     }
10884     }
10885     for (i = backwardMostMove; i < upto; i++) {
10886         SendMoveToProgram(i, cps);
10887     }
10888 }
10889
10890
10891 int
10892 ResurrectChessProgram ()
10893 {
10894      /* The chess program may have exited.
10895         If so, restart it and feed it all the moves made so far. */
10896     static int doInit = 0;
10897
10898     if (appData.noChessProgram) return 1;
10899
10900     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10901         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10902         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10903         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10904     } else {
10905         if (first.pr != NoProc) return 1;
10906         StartChessProgram(&first);
10907     }
10908     InitChessProgram(&first, FALSE);
10909     FeedMovesToProgram(&first, currentMove);
10910
10911     if (!first.sendTime) {
10912         /* can't tell gnuchess what its clock should read,
10913            so we bow to its notion. */
10914         ResetClocks();
10915         timeRemaining[0][currentMove] = whiteTimeRemaining;
10916         timeRemaining[1][currentMove] = blackTimeRemaining;
10917     }
10918
10919     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10920                 appData.icsEngineAnalyze) && first.analysisSupport) {
10921       SendToProgram("analyze\n", &first);
10922       first.analyzing = TRUE;
10923     }
10924     return 1;
10925 }
10926
10927 /*
10928  * Button procedures
10929  */
10930 void
10931 Reset (int redraw, int init)
10932 {
10933     int i;
10934
10935     if (appData.debugMode) {
10936         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10937                 redraw, init, gameMode);
10938     }
10939     CleanupTail(); // [HGM] vari: delete any stored variations
10940     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10941     pausing = pauseExamInvalid = FALSE;
10942     startedFromSetupPosition = blackPlaysFirst = FALSE;
10943     firstMove = TRUE;
10944     whiteFlag = blackFlag = FALSE;
10945     userOfferedDraw = FALSE;
10946     hintRequested = bookRequested = FALSE;
10947     first.maybeThinking = FALSE;
10948     second.maybeThinking = FALSE;
10949     first.bookSuspend = FALSE; // [HGM] book
10950     second.bookSuspend = FALSE;
10951     thinkOutput[0] = NULLCHAR;
10952     lastHint[0] = NULLCHAR;
10953     ClearGameInfo(&gameInfo);
10954     gameInfo.variant = StringToVariant(appData.variant);
10955     ics_user_moved = ics_clock_paused = FALSE;
10956     ics_getting_history = H_FALSE;
10957     ics_gamenum = -1;
10958     white_holding[0] = black_holding[0] = NULLCHAR;
10959     ClearProgramStats();
10960     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10961
10962     ResetFrontEnd();
10963     ClearHighlights();
10964     flipView = appData.flipView;
10965     ClearPremoveHighlights();
10966     gotPremove = FALSE;
10967     alarmSounded = FALSE;
10968
10969     GameEnds(EndOfFile, NULL, GE_PLAYER);
10970     if(appData.serverMovesName != NULL) {
10971         /* [HGM] prepare to make moves file for broadcasting */
10972         clock_t t = clock();
10973         if(serverMoves != NULL) fclose(serverMoves);
10974         serverMoves = fopen(appData.serverMovesName, "r");
10975         if(serverMoves != NULL) {
10976             fclose(serverMoves);
10977             /* delay 15 sec before overwriting, so all clients can see end */
10978             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10979         }
10980         serverMoves = fopen(appData.serverMovesName, "w");
10981     }
10982
10983     ExitAnalyzeMode();
10984     gameMode = BeginningOfGame;
10985     ModeHighlight();
10986     if(appData.icsActive) gameInfo.variant = VariantNormal;
10987     currentMove = forwardMostMove = backwardMostMove = 0;
10988     MarkTargetSquares(1);
10989     InitPosition(redraw);
10990     for (i = 0; i < MAX_MOVES; i++) {
10991         if (commentList[i] != NULL) {
10992             free(commentList[i]);
10993             commentList[i] = NULL;
10994         }
10995     }
10996     ResetClocks();
10997     timeRemaining[0][0] = whiteTimeRemaining;
10998     timeRemaining[1][0] = blackTimeRemaining;
10999
11000     if (first.pr == NoProc) {
11001         StartChessProgram(&first);
11002     }
11003     if (init) {
11004             InitChessProgram(&first, startedFromSetupPosition);
11005     }
11006     DisplayTitle("");
11007     DisplayMessage("", "");
11008     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11009     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11010     ClearMap();        // [HGM] exclude: invalidate map
11011 }
11012
11013 void
11014 AutoPlayGameLoop ()
11015 {
11016     for (;;) {
11017         if (!AutoPlayOneMove())
11018           return;
11019         if (matchMode || appData.timeDelay == 0)
11020           continue;
11021         if (appData.timeDelay < 0)
11022           return;
11023         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11024         break;
11025     }
11026 }
11027
11028 void
11029 AnalyzeNextGame()
11030 {
11031     ReloadGame(1); // next game
11032 }
11033
11034 int
11035 AutoPlayOneMove ()
11036 {
11037     int fromX, fromY, toX, toY;
11038
11039     if (appData.debugMode) {
11040       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11041     }
11042
11043     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11044       return FALSE;
11045
11046     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11047       pvInfoList[currentMove].depth = programStats.depth;
11048       pvInfoList[currentMove].score = programStats.score;
11049       pvInfoList[currentMove].time  = 0;
11050       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11051     }
11052
11053     if (currentMove >= forwardMostMove) {
11054       if(gameMode == AnalyzeFile) {
11055           if(appData.loadGameIndex == -1) {
11056             GameEnds(EndOfFile, NULL, GE_FILE);
11057           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11058           } else {
11059           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11060         }
11061       }
11062 //      gameMode = EndOfGame;
11063 //      ModeHighlight();
11064
11065       /* [AS] Clear current move marker at the end of a game */
11066       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11067
11068       return FALSE;
11069     }
11070
11071     toX = moveList[currentMove][2] - AAA;
11072     toY = moveList[currentMove][3] - ONE;
11073
11074     if (moveList[currentMove][1] == '@') {
11075         if (appData.highlightLastMove) {
11076             SetHighlights(-1, -1, toX, toY);
11077         }
11078     } else {
11079         fromX = moveList[currentMove][0] - AAA;
11080         fromY = moveList[currentMove][1] - ONE;
11081
11082         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11083
11084         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11085
11086         if (appData.highlightLastMove) {
11087             SetHighlights(fromX, fromY, toX, toY);
11088         }
11089     }
11090     DisplayMove(currentMove);
11091     SendMoveToProgram(currentMove++, &first);
11092     DisplayBothClocks();
11093     DrawPosition(FALSE, boards[currentMove]);
11094     // [HGM] PV info: always display, routine tests if empty
11095     DisplayComment(currentMove - 1, commentList[currentMove]);
11096     return TRUE;
11097 }
11098
11099
11100 int
11101 LoadGameOneMove (ChessMove readAhead)
11102 {
11103     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11104     char promoChar = NULLCHAR;
11105     ChessMove moveType;
11106     char move[MSG_SIZ];
11107     char *p, *q;
11108
11109     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11110         gameMode != AnalyzeMode && gameMode != Training) {
11111         gameFileFP = NULL;
11112         return FALSE;
11113     }
11114
11115     yyboardindex = forwardMostMove;
11116     if (readAhead != EndOfFile) {
11117       moveType = readAhead;
11118     } else {
11119       if (gameFileFP == NULL)
11120           return FALSE;
11121       moveType = (ChessMove) Myylex();
11122     }
11123
11124     done = FALSE;
11125     switch (moveType) {
11126       case Comment:
11127         if (appData.debugMode)
11128           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11129         p = yy_text;
11130
11131         /* append the comment but don't display it */
11132         AppendComment(currentMove, p, FALSE);
11133         return TRUE;
11134
11135       case WhiteCapturesEnPassant:
11136       case BlackCapturesEnPassant:
11137       case WhitePromotion:
11138       case BlackPromotion:
11139       case WhiteNonPromotion:
11140       case BlackNonPromotion:
11141       case NormalMove:
11142       case WhiteKingSideCastle:
11143       case WhiteQueenSideCastle:
11144       case BlackKingSideCastle:
11145       case BlackQueenSideCastle:
11146       case WhiteKingSideCastleWild:
11147       case WhiteQueenSideCastleWild:
11148       case BlackKingSideCastleWild:
11149       case BlackQueenSideCastleWild:
11150       /* PUSH Fabien */
11151       case WhiteHSideCastleFR:
11152       case WhiteASideCastleFR:
11153       case BlackHSideCastleFR:
11154       case BlackASideCastleFR:
11155       /* POP Fabien */
11156         if (appData.debugMode)
11157           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11158         fromX = currentMoveString[0] - AAA;
11159         fromY = currentMoveString[1] - ONE;
11160         toX = currentMoveString[2] - AAA;
11161         toY = currentMoveString[3] - ONE;
11162         promoChar = currentMoveString[4];
11163         break;
11164
11165       case WhiteDrop:
11166       case BlackDrop:
11167         if (appData.debugMode)
11168           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11169         fromX = moveType == WhiteDrop ?
11170           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11171         (int) CharToPiece(ToLower(currentMoveString[0]));
11172         fromY = DROP_RANK;
11173         toX = currentMoveString[2] - AAA;
11174         toY = currentMoveString[3] - ONE;
11175         break;
11176
11177       case WhiteWins:
11178       case BlackWins:
11179       case GameIsDrawn:
11180       case GameUnfinished:
11181         if (appData.debugMode)
11182           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11183         p = strchr(yy_text, '{');
11184         if (p == NULL) p = strchr(yy_text, '(');
11185         if (p == NULL) {
11186             p = yy_text;
11187             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11188         } else {
11189             q = strchr(p, *p == '{' ? '}' : ')');
11190             if (q != NULL) *q = NULLCHAR;
11191             p++;
11192         }
11193         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11194         GameEnds(moveType, p, GE_FILE);
11195         done = TRUE;
11196         if (cmailMsgLoaded) {
11197             ClearHighlights();
11198             flipView = WhiteOnMove(currentMove);
11199             if (moveType == GameUnfinished) flipView = !flipView;
11200             if (appData.debugMode)
11201               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11202         }
11203         break;
11204
11205       case EndOfFile:
11206         if (appData.debugMode)
11207           fprintf(debugFP, "Parser hit end of file\n");
11208         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11209           case MT_NONE:
11210           case MT_CHECK:
11211             break;
11212           case MT_CHECKMATE:
11213           case MT_STAINMATE:
11214             if (WhiteOnMove(currentMove)) {
11215                 GameEnds(BlackWins, "Black mates", GE_FILE);
11216             } else {
11217                 GameEnds(WhiteWins, "White mates", GE_FILE);
11218             }
11219             break;
11220           case MT_STALEMATE:
11221             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11222             break;
11223         }
11224         done = TRUE;
11225         break;
11226
11227       case MoveNumberOne:
11228         if (lastLoadGameStart == GNUChessGame) {
11229             /* GNUChessGames have numbers, but they aren't move numbers */
11230             if (appData.debugMode)
11231               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11232                       yy_text, (int) moveType);
11233             return LoadGameOneMove(EndOfFile); /* tail recursion */
11234         }
11235         /* else fall thru */
11236
11237       case XBoardGame:
11238       case GNUChessGame:
11239       case PGNTag:
11240         /* Reached start of next game in file */
11241         if (appData.debugMode)
11242           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11243         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11244           case MT_NONE:
11245           case MT_CHECK:
11246             break;
11247           case MT_CHECKMATE:
11248           case MT_STAINMATE:
11249             if (WhiteOnMove(currentMove)) {
11250                 GameEnds(BlackWins, "Black mates", GE_FILE);
11251             } else {
11252                 GameEnds(WhiteWins, "White mates", GE_FILE);
11253             }
11254             break;
11255           case MT_STALEMATE:
11256             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11257             break;
11258         }
11259         done = TRUE;
11260         break;
11261
11262       case PositionDiagram:     /* should not happen; ignore */
11263       case ElapsedTime:         /* ignore */
11264       case NAG:                 /* ignore */
11265         if (appData.debugMode)
11266           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11267                   yy_text, (int) moveType);
11268         return LoadGameOneMove(EndOfFile); /* tail recursion */
11269
11270       case IllegalMove:
11271         if (appData.testLegality) {
11272             if (appData.debugMode)
11273               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11274             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11275                     (forwardMostMove / 2) + 1,
11276                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11277             DisplayError(move, 0);
11278             done = TRUE;
11279         } else {
11280             if (appData.debugMode)
11281               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11282                       yy_text, currentMoveString);
11283             fromX = currentMoveString[0] - AAA;
11284             fromY = currentMoveString[1] - ONE;
11285             toX = currentMoveString[2] - AAA;
11286             toY = currentMoveString[3] - ONE;
11287             promoChar = currentMoveString[4];
11288         }
11289         break;
11290
11291       case AmbiguousMove:
11292         if (appData.debugMode)
11293           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11294         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11295                 (forwardMostMove / 2) + 1,
11296                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11297         DisplayError(move, 0);
11298         done = TRUE;
11299         break;
11300
11301       default:
11302       case ImpossibleMove:
11303         if (appData.debugMode)
11304           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11305         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11306                 (forwardMostMove / 2) + 1,
11307                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11308         DisplayError(move, 0);
11309         done = TRUE;
11310         break;
11311     }
11312
11313     if (done) {
11314         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11315             DrawPosition(FALSE, boards[currentMove]);
11316             DisplayBothClocks();
11317             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11318               DisplayComment(currentMove - 1, commentList[currentMove]);
11319         }
11320         (void) StopLoadGameTimer();
11321         gameFileFP = NULL;
11322         cmailOldMove = forwardMostMove;
11323         return FALSE;
11324     } else {
11325         /* currentMoveString is set as a side-effect of yylex */
11326
11327         thinkOutput[0] = NULLCHAR;
11328         MakeMove(fromX, fromY, toX, toY, promoChar);
11329         currentMove = forwardMostMove;
11330         return TRUE;
11331     }
11332 }
11333
11334 /* Load the nth game from the given file */
11335 int
11336 LoadGameFromFile (char *filename, int n, char *title, int useList)
11337 {
11338     FILE *f;
11339     char buf[MSG_SIZ];
11340
11341     if (strcmp(filename, "-") == 0) {
11342         f = stdin;
11343         title = "stdin";
11344     } else {
11345         f = fopen(filename, "rb");
11346         if (f == NULL) {
11347           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11348             DisplayError(buf, errno);
11349             return FALSE;
11350         }
11351     }
11352     if (fseek(f, 0, 0) == -1) {
11353         /* f is not seekable; probably a pipe */
11354         useList = FALSE;
11355     }
11356     if (useList && n == 0) {
11357         int error = GameListBuild(f);
11358         if (error) {
11359             DisplayError(_("Cannot build game list"), error);
11360         } else if (!ListEmpty(&gameList) &&
11361                    ((ListGame *) gameList.tailPred)->number > 1) {
11362             GameListPopUp(f, title);
11363             return TRUE;
11364         }
11365         GameListDestroy();
11366         n = 1;
11367     }
11368     if (n == 0) n = 1;
11369     return LoadGame(f, n, title, FALSE);
11370 }
11371
11372
11373 void
11374 MakeRegisteredMove ()
11375 {
11376     int fromX, fromY, toX, toY;
11377     char promoChar;
11378     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11379         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11380           case CMAIL_MOVE:
11381           case CMAIL_DRAW:
11382             if (appData.debugMode)
11383               fprintf(debugFP, "Restoring %s for game %d\n",
11384                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11385
11386             thinkOutput[0] = NULLCHAR;
11387             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11388             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11389             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11390             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11391             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11392             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11393             MakeMove(fromX, fromY, toX, toY, promoChar);
11394             ShowMove(fromX, fromY, toX, toY);
11395
11396             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11397               case MT_NONE:
11398               case MT_CHECK:
11399                 break;
11400
11401               case MT_CHECKMATE:
11402               case MT_STAINMATE:
11403                 if (WhiteOnMove(currentMove)) {
11404                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11405                 } else {
11406                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11407                 }
11408                 break;
11409
11410               case MT_STALEMATE:
11411                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11412                 break;
11413             }
11414
11415             break;
11416
11417           case CMAIL_RESIGN:
11418             if (WhiteOnMove(currentMove)) {
11419                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11420             } else {
11421                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11422             }
11423             break;
11424
11425           case CMAIL_ACCEPT:
11426             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11427             break;
11428
11429           default:
11430             break;
11431         }
11432     }
11433
11434     return;
11435 }
11436
11437 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11438 int
11439 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11440 {
11441     int retVal;
11442
11443     if (gameNumber > nCmailGames) {
11444         DisplayError(_("No more games in this message"), 0);
11445         return FALSE;
11446     }
11447     if (f == lastLoadGameFP) {
11448         int offset = gameNumber - lastLoadGameNumber;
11449         if (offset == 0) {
11450             cmailMsg[0] = NULLCHAR;
11451             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11452                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11453                 nCmailMovesRegistered--;
11454             }
11455             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11456             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11457                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11458             }
11459         } else {
11460             if (! RegisterMove()) return FALSE;
11461         }
11462     }
11463
11464     retVal = LoadGame(f, gameNumber, title, useList);
11465
11466     /* Make move registered during previous look at this game, if any */
11467     MakeRegisteredMove();
11468
11469     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11470         commentList[currentMove]
11471           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11472         DisplayComment(currentMove - 1, commentList[currentMove]);
11473     }
11474
11475     return retVal;
11476 }
11477
11478 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11479 int
11480 ReloadGame (int offset)
11481 {
11482     int gameNumber = lastLoadGameNumber + offset;
11483     if (lastLoadGameFP == NULL) {
11484         DisplayError(_("No game has been loaded yet"), 0);
11485         return FALSE;
11486     }
11487     if (gameNumber <= 0) {
11488         DisplayError(_("Can't back up any further"), 0);
11489         return FALSE;
11490     }
11491     if (cmailMsgLoaded) {
11492         return CmailLoadGame(lastLoadGameFP, gameNumber,
11493                              lastLoadGameTitle, lastLoadGameUseList);
11494     } else {
11495         return LoadGame(lastLoadGameFP, gameNumber,
11496                         lastLoadGameTitle, lastLoadGameUseList);
11497     }
11498 }
11499
11500 int keys[EmptySquare+1];
11501
11502 int
11503 PositionMatches (Board b1, Board b2)
11504 {
11505     int r, f, sum=0;
11506     switch(appData.searchMode) {
11507         case 1: return CompareWithRights(b1, b2);
11508         case 2:
11509             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11510                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11511             }
11512             return TRUE;
11513         case 3:
11514             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11515               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11516                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11517             }
11518             return sum==0;
11519         case 4:
11520             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11521                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11522             }
11523             return sum==0;
11524     }
11525     return TRUE;
11526 }
11527
11528 #define Q_PROMO  4
11529 #define Q_EP     3
11530 #define Q_BCASTL 2
11531 #define Q_WCASTL 1
11532
11533 int pieceList[256], quickBoard[256];
11534 ChessSquare pieceType[256] = { EmptySquare };
11535 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11536 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11537 int soughtTotal, turn;
11538 Boolean epOK, flipSearch;
11539
11540 typedef struct {
11541     unsigned char piece, to;
11542 } Move;
11543
11544 #define DSIZE (250000)
11545
11546 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11547 Move *moveDatabase = initialSpace;
11548 unsigned int movePtr, dataSize = DSIZE;
11549
11550 int
11551 MakePieceList (Board board, int *counts)
11552 {
11553     int r, f, n=Q_PROMO, total=0;
11554     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11555     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11556         int sq = f + (r<<4);
11557         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11558             quickBoard[sq] = ++n;
11559             pieceList[n] = sq;
11560             pieceType[n] = board[r][f];
11561             counts[board[r][f]]++;
11562             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11563             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11564             total++;
11565         }
11566     }
11567     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11568     return total;
11569 }
11570
11571 void
11572 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11573 {
11574     int sq = fromX + (fromY<<4);
11575     int piece = quickBoard[sq];
11576     quickBoard[sq] = 0;
11577     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11578     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11579         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11580         moveDatabase[movePtr++].piece = Q_WCASTL;
11581         quickBoard[sq] = piece;
11582         piece = quickBoard[from]; quickBoard[from] = 0;
11583         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11584     } else
11585     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11586         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11587         moveDatabase[movePtr++].piece = Q_BCASTL;
11588         quickBoard[sq] = piece;
11589         piece = quickBoard[from]; quickBoard[from] = 0;
11590         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11591     } else
11592     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11593         quickBoard[(fromY<<4)+toX] = 0;
11594         moveDatabase[movePtr].piece = Q_EP;
11595         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11596         moveDatabase[movePtr].to = sq;
11597     } else
11598     if(promoPiece != pieceType[piece]) {
11599         moveDatabase[movePtr++].piece = Q_PROMO;
11600         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11601     }
11602     moveDatabase[movePtr].piece = piece;
11603     quickBoard[sq] = piece;
11604     movePtr++;
11605 }
11606
11607 int
11608 PackGame (Board board)
11609 {
11610     Move *newSpace = NULL;
11611     moveDatabase[movePtr].piece = 0; // terminate previous game
11612     if(movePtr > dataSize) {
11613         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11614         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11615         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11616         if(newSpace) {
11617             int i;
11618             Move *p = moveDatabase, *q = newSpace;
11619             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11620             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11621             moveDatabase = newSpace;
11622         } else { // calloc failed, we must be out of memory. Too bad...
11623             dataSize = 0; // prevent calloc events for all subsequent games
11624             return 0;     // and signal this one isn't cached
11625         }
11626     }
11627     movePtr++;
11628     MakePieceList(board, counts);
11629     return movePtr;
11630 }
11631
11632 int
11633 QuickCompare (Board board, int *minCounts, int *maxCounts)
11634 {   // compare according to search mode
11635     int r, f;
11636     switch(appData.searchMode)
11637     {
11638       case 1: // exact position match
11639         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11640         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11641             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11642         }
11643         break;
11644       case 2: // can have extra material on empty squares
11645         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11646             if(board[r][f] == EmptySquare) continue;
11647             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11648         }
11649         break;
11650       case 3: // material with exact Pawn structure
11651         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11652             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11653             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11654         } // fall through to material comparison
11655       case 4: // exact material
11656         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11657         break;
11658       case 6: // material range with given imbalance
11659         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11660         // fall through to range comparison
11661       case 5: // material range
11662         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11663     }
11664     return TRUE;
11665 }
11666
11667 int
11668 QuickScan (Board board, Move *move)
11669 {   // reconstruct game,and compare all positions in it
11670     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11671     do {
11672         int piece = move->piece;
11673         int to = move->to, from = pieceList[piece];
11674         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11675           if(!piece) return -1;
11676           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11677             piece = (++move)->piece;
11678             from = pieceList[piece];
11679             counts[pieceType[piece]]--;
11680             pieceType[piece] = (ChessSquare) move->to;
11681             counts[move->to]++;
11682           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11683             counts[pieceType[quickBoard[to]]]--;
11684             quickBoard[to] = 0; total--;
11685             move++;
11686             continue;
11687           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11688             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11689             from  = pieceList[piece]; // so this must be King
11690             quickBoard[from] = 0;
11691             pieceList[piece] = to;
11692             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11693             quickBoard[from] = 0; // rook
11694             quickBoard[to] = piece;
11695             to = move->to; piece = move->piece;
11696             goto aftercastle;
11697           }
11698         }
11699         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11700         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11701         quickBoard[from] = 0;
11702       aftercastle:
11703         quickBoard[to] = piece;
11704         pieceList[piece] = to;
11705         cnt++; turn ^= 3;
11706         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11707            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11708            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11709                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11710           ) {
11711             static int lastCounts[EmptySquare+1];
11712             int i;
11713             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11714             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11715         } else stretch = 0;
11716         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11717         move++;
11718     } while(1);
11719 }
11720
11721 void
11722 InitSearch ()
11723 {
11724     int r, f;
11725     flipSearch = FALSE;
11726     CopyBoard(soughtBoard, boards[currentMove]);
11727     soughtTotal = MakePieceList(soughtBoard, maxSought);
11728     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11729     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11730     CopyBoard(reverseBoard, boards[currentMove]);
11731     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11732         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11733         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11734         reverseBoard[r][f] = piece;
11735     }
11736     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11737     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11738     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11739                  || (boards[currentMove][CASTLING][2] == NoRights || 
11740                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11741                  && (boards[currentMove][CASTLING][5] == NoRights || 
11742                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11743       ) {
11744         flipSearch = TRUE;
11745         CopyBoard(flipBoard, soughtBoard);
11746         CopyBoard(rotateBoard, reverseBoard);
11747         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11748             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11749             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11750         }
11751     }
11752     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11753     if(appData.searchMode >= 5) {
11754         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11755         MakePieceList(soughtBoard, minSought);
11756         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11757     }
11758     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11759         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11760 }
11761
11762 GameInfo dummyInfo;
11763
11764 int
11765 GameContainsPosition (FILE *f, ListGame *lg)
11766 {
11767     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11768     int fromX, fromY, toX, toY;
11769     char promoChar;
11770     static int initDone=FALSE;
11771
11772     // weed out games based on numerical tag comparison
11773     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11774     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11775     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11776     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11777     if(!initDone) {
11778         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11779         initDone = TRUE;
11780     }
11781     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11782     else CopyBoard(boards[scratch], initialPosition); // default start position
11783     if(lg->moves) {
11784         turn = btm + 1;
11785         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11786         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11787     }
11788     if(btm) plyNr++;
11789     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11790     fseek(f, lg->offset, 0);
11791     yynewfile(f);
11792     while(1) {
11793         yyboardindex = scratch;
11794         quickFlag = plyNr+1;
11795         next = Myylex();
11796         quickFlag = 0;
11797         switch(next) {
11798             case PGNTag:
11799                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11800             default:
11801                 continue;
11802
11803             case XBoardGame:
11804             case GNUChessGame:
11805                 if(plyNr) return -1; // after we have seen moves, this is for new game
11806               continue;
11807
11808             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11809             case ImpossibleMove:
11810             case WhiteWins: // game ends here with these four
11811             case BlackWins:
11812             case GameIsDrawn:
11813             case GameUnfinished:
11814                 return -1;
11815
11816             case IllegalMove:
11817                 if(appData.testLegality) return -1;
11818             case WhiteCapturesEnPassant:
11819             case BlackCapturesEnPassant:
11820             case WhitePromotion:
11821             case BlackPromotion:
11822             case WhiteNonPromotion:
11823             case BlackNonPromotion:
11824             case NormalMove:
11825             case WhiteKingSideCastle:
11826             case WhiteQueenSideCastle:
11827             case BlackKingSideCastle:
11828             case BlackQueenSideCastle:
11829             case WhiteKingSideCastleWild:
11830             case WhiteQueenSideCastleWild:
11831             case BlackKingSideCastleWild:
11832             case BlackQueenSideCastleWild:
11833             case WhiteHSideCastleFR:
11834             case WhiteASideCastleFR:
11835             case BlackHSideCastleFR:
11836             case BlackASideCastleFR:
11837                 fromX = currentMoveString[0] - AAA;
11838                 fromY = currentMoveString[1] - ONE;
11839                 toX = currentMoveString[2] - AAA;
11840                 toY = currentMoveString[3] - ONE;
11841                 promoChar = currentMoveString[4];
11842                 break;
11843             case WhiteDrop:
11844             case BlackDrop:
11845                 fromX = next == WhiteDrop ?
11846                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11847                   (int) CharToPiece(ToLower(currentMoveString[0]));
11848                 fromY = DROP_RANK;
11849                 toX = currentMoveString[2] - AAA;
11850                 toY = currentMoveString[3] - ONE;
11851                 promoChar = 0;
11852                 break;
11853         }
11854         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11855         plyNr++;
11856         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11857         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11858         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11859         if(appData.findMirror) {
11860             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11861             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11862         }
11863     }
11864 }
11865
11866 /* Load the nth game from open file f */
11867 int
11868 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11869 {
11870     ChessMove cm;
11871     char buf[MSG_SIZ];
11872     int gn = gameNumber;
11873     ListGame *lg = NULL;
11874     int numPGNTags = 0;
11875     int err, pos = -1;
11876     GameMode oldGameMode;
11877     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11878
11879     if (appData.debugMode)
11880         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11881
11882     if (gameMode == Training )
11883         SetTrainingModeOff();
11884
11885     oldGameMode = gameMode;
11886     if (gameMode != BeginningOfGame) {
11887       Reset(FALSE, TRUE);
11888     }
11889
11890     gameFileFP = f;
11891     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11892         fclose(lastLoadGameFP);
11893     }
11894
11895     if (useList) {
11896         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11897
11898         if (lg) {
11899             fseek(f, lg->offset, 0);
11900             GameListHighlight(gameNumber);
11901             pos = lg->position;
11902             gn = 1;
11903         }
11904         else {
11905             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11906               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11907             else
11908             DisplayError(_("Game number out of range"), 0);
11909             return FALSE;
11910         }
11911     } else {
11912         GameListDestroy();
11913         if (fseek(f, 0, 0) == -1) {
11914             if (f == lastLoadGameFP ?
11915                 gameNumber == lastLoadGameNumber + 1 :
11916                 gameNumber == 1) {
11917                 gn = 1;
11918             } else {
11919                 DisplayError(_("Can't seek on game file"), 0);
11920                 return FALSE;
11921             }
11922         }
11923     }
11924     lastLoadGameFP = f;
11925     lastLoadGameNumber = gameNumber;
11926     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11927     lastLoadGameUseList = useList;
11928
11929     yynewfile(f);
11930
11931     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11932       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11933                 lg->gameInfo.black);
11934             DisplayTitle(buf);
11935     } else if (*title != NULLCHAR) {
11936         if (gameNumber > 1) {
11937           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11938             DisplayTitle(buf);
11939         } else {
11940             DisplayTitle(title);
11941         }
11942     }
11943
11944     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11945         gameMode = PlayFromGameFile;
11946         ModeHighlight();
11947     }
11948
11949     currentMove = forwardMostMove = backwardMostMove = 0;
11950     CopyBoard(boards[0], initialPosition);
11951     StopClocks();
11952
11953     /*
11954      * Skip the first gn-1 games in the file.
11955      * Also skip over anything that precedes an identifiable
11956      * start of game marker, to avoid being confused by
11957      * garbage at the start of the file.  Currently
11958      * recognized start of game markers are the move number "1",
11959      * the pattern "gnuchess .* game", the pattern
11960      * "^[#;%] [^ ]* game file", and a PGN tag block.
11961      * A game that starts with one of the latter two patterns
11962      * will also have a move number 1, possibly
11963      * following a position diagram.
11964      * 5-4-02: Let's try being more lenient and allowing a game to
11965      * start with an unnumbered move.  Does that break anything?
11966      */
11967     cm = lastLoadGameStart = EndOfFile;
11968     while (gn > 0) {
11969         yyboardindex = forwardMostMove;
11970         cm = (ChessMove) Myylex();
11971         switch (cm) {
11972           case EndOfFile:
11973             if (cmailMsgLoaded) {
11974                 nCmailGames = CMAIL_MAX_GAMES - gn;
11975             } else {
11976                 Reset(TRUE, TRUE);
11977                 DisplayError(_("Game not found in file"), 0);
11978             }
11979             return FALSE;
11980
11981           case GNUChessGame:
11982           case XBoardGame:
11983             gn--;
11984             lastLoadGameStart = cm;
11985             break;
11986
11987           case MoveNumberOne:
11988             switch (lastLoadGameStart) {
11989               case GNUChessGame:
11990               case XBoardGame:
11991               case PGNTag:
11992                 break;
11993               case MoveNumberOne:
11994               case EndOfFile:
11995                 gn--;           /* count this game */
11996                 lastLoadGameStart = cm;
11997                 break;
11998               default:
11999                 /* impossible */
12000                 break;
12001             }
12002             break;
12003
12004           case PGNTag:
12005             switch (lastLoadGameStart) {
12006               case GNUChessGame:
12007               case PGNTag:
12008               case MoveNumberOne:
12009               case EndOfFile:
12010                 gn--;           /* count this game */
12011                 lastLoadGameStart = cm;
12012                 break;
12013               case XBoardGame:
12014                 lastLoadGameStart = cm; /* game counted already */
12015                 break;
12016               default:
12017                 /* impossible */
12018                 break;
12019             }
12020             if (gn > 0) {
12021                 do {
12022                     yyboardindex = forwardMostMove;
12023                     cm = (ChessMove) Myylex();
12024                 } while (cm == PGNTag || cm == Comment);
12025             }
12026             break;
12027
12028           case WhiteWins:
12029           case BlackWins:
12030           case GameIsDrawn:
12031             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12032                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12033                     != CMAIL_OLD_RESULT) {
12034                     nCmailResults ++ ;
12035                     cmailResult[  CMAIL_MAX_GAMES
12036                                 - gn - 1] = CMAIL_OLD_RESULT;
12037                 }
12038             }
12039             break;
12040
12041           case NormalMove:
12042             /* Only a NormalMove can be at the start of a game
12043              * without a position diagram. */
12044             if (lastLoadGameStart == EndOfFile ) {
12045               gn--;
12046               lastLoadGameStart = MoveNumberOne;
12047             }
12048             break;
12049
12050           default:
12051             break;
12052         }
12053     }
12054
12055     if (appData.debugMode)
12056       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12057
12058     if (cm == XBoardGame) {
12059         /* Skip any header junk before position diagram and/or move 1 */
12060         for (;;) {
12061             yyboardindex = forwardMostMove;
12062             cm = (ChessMove) Myylex();
12063
12064             if (cm == EndOfFile ||
12065                 cm == GNUChessGame || cm == XBoardGame) {
12066                 /* Empty game; pretend end-of-file and handle later */
12067                 cm = EndOfFile;
12068                 break;
12069             }
12070
12071             if (cm == MoveNumberOne || cm == PositionDiagram ||
12072                 cm == PGNTag || cm == Comment)
12073               break;
12074         }
12075     } else if (cm == GNUChessGame) {
12076         if (gameInfo.event != NULL) {
12077             free(gameInfo.event);
12078         }
12079         gameInfo.event = StrSave(yy_text);
12080     }
12081
12082     startedFromSetupPosition = FALSE;
12083     while (cm == PGNTag) {
12084         if (appData.debugMode)
12085           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12086         err = ParsePGNTag(yy_text, &gameInfo);
12087         if (!err) numPGNTags++;
12088
12089         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12090         if(gameInfo.variant != oldVariant) {
12091             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12092             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12093             InitPosition(TRUE);
12094             oldVariant = gameInfo.variant;
12095             if (appData.debugMode)
12096               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12097         }
12098
12099
12100         if (gameInfo.fen != NULL) {
12101           Board initial_position;
12102           startedFromSetupPosition = TRUE;
12103           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12104             Reset(TRUE, TRUE);
12105             DisplayError(_("Bad FEN position in file"), 0);
12106             return FALSE;
12107           }
12108           CopyBoard(boards[0], initial_position);
12109           if (blackPlaysFirst) {
12110             currentMove = forwardMostMove = backwardMostMove = 1;
12111             CopyBoard(boards[1], initial_position);
12112             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12113             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12114             timeRemaining[0][1] = whiteTimeRemaining;
12115             timeRemaining[1][1] = blackTimeRemaining;
12116             if (commentList[0] != NULL) {
12117               commentList[1] = commentList[0];
12118               commentList[0] = NULL;
12119             }
12120           } else {
12121             currentMove = forwardMostMove = backwardMostMove = 0;
12122           }
12123           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12124           {   int i;
12125               initialRulePlies = FENrulePlies;
12126               for( i=0; i< nrCastlingRights; i++ )
12127                   initialRights[i] = initial_position[CASTLING][i];
12128           }
12129           yyboardindex = forwardMostMove;
12130           free(gameInfo.fen);
12131           gameInfo.fen = NULL;
12132         }
12133
12134         yyboardindex = forwardMostMove;
12135         cm = (ChessMove) Myylex();
12136
12137         /* Handle comments interspersed among the tags */
12138         while (cm == Comment) {
12139             char *p;
12140             if (appData.debugMode)
12141               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12142             p = yy_text;
12143             AppendComment(currentMove, p, FALSE);
12144             yyboardindex = forwardMostMove;
12145             cm = (ChessMove) Myylex();
12146         }
12147     }
12148
12149     /* don't rely on existence of Event tag since if game was
12150      * pasted from clipboard the Event tag may not exist
12151      */
12152     if (numPGNTags > 0){
12153         char *tags;
12154         if (gameInfo.variant == VariantNormal) {
12155           VariantClass v = StringToVariant(gameInfo.event);
12156           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12157           if(v < VariantShogi) gameInfo.variant = v;
12158         }
12159         if (!matchMode) {
12160           if( appData.autoDisplayTags ) {
12161             tags = PGNTags(&gameInfo);
12162             TagsPopUp(tags, CmailMsg());
12163             free(tags);
12164           }
12165         }
12166     } else {
12167         /* Make something up, but don't display it now */
12168         SetGameInfo();
12169         TagsPopDown();
12170     }
12171
12172     if (cm == PositionDiagram) {
12173         int i, j;
12174         char *p;
12175         Board initial_position;
12176
12177         if (appData.debugMode)
12178           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12179
12180         if (!startedFromSetupPosition) {
12181             p = yy_text;
12182             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12183               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12184                 switch (*p) {
12185                   case '{':
12186                   case '[':
12187                   case '-':
12188                   case ' ':
12189                   case '\t':
12190                   case '\n':
12191                   case '\r':
12192                     break;
12193                   default:
12194                     initial_position[i][j++] = CharToPiece(*p);
12195                     break;
12196                 }
12197             while (*p == ' ' || *p == '\t' ||
12198                    *p == '\n' || *p == '\r') p++;
12199
12200             if (strncmp(p, "black", strlen("black"))==0)
12201               blackPlaysFirst = TRUE;
12202             else
12203               blackPlaysFirst = FALSE;
12204             startedFromSetupPosition = TRUE;
12205
12206             CopyBoard(boards[0], initial_position);
12207             if (blackPlaysFirst) {
12208                 currentMove = forwardMostMove = backwardMostMove = 1;
12209                 CopyBoard(boards[1], initial_position);
12210                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12211                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12212                 timeRemaining[0][1] = whiteTimeRemaining;
12213                 timeRemaining[1][1] = blackTimeRemaining;
12214                 if (commentList[0] != NULL) {
12215                     commentList[1] = commentList[0];
12216                     commentList[0] = NULL;
12217                 }
12218             } else {
12219                 currentMove = forwardMostMove = backwardMostMove = 0;
12220             }
12221         }
12222         yyboardindex = forwardMostMove;
12223         cm = (ChessMove) Myylex();
12224     }
12225
12226     if (first.pr == NoProc) {
12227         StartChessProgram(&first);
12228     }
12229     InitChessProgram(&first, FALSE);
12230     SendToProgram("force\n", &first);
12231     if (startedFromSetupPosition) {
12232         SendBoard(&first, forwardMostMove);
12233     if (appData.debugMode) {
12234         fprintf(debugFP, "Load Game\n");
12235     }
12236         DisplayBothClocks();
12237     }
12238
12239     /* [HGM] server: flag to write setup moves in broadcast file as one */
12240     loadFlag = appData.suppressLoadMoves;
12241
12242     while (cm == Comment) {
12243         char *p;
12244         if (appData.debugMode)
12245           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12246         p = yy_text;
12247         AppendComment(currentMove, p, FALSE);
12248         yyboardindex = forwardMostMove;
12249         cm = (ChessMove) Myylex();
12250     }
12251
12252     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12253         cm == WhiteWins || cm == BlackWins ||
12254         cm == GameIsDrawn || cm == GameUnfinished) {
12255         DisplayMessage("", _("No moves in game"));
12256         if (cmailMsgLoaded) {
12257             if (appData.debugMode)
12258               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12259             ClearHighlights();
12260             flipView = FALSE;
12261         }
12262         DrawPosition(FALSE, boards[currentMove]);
12263         DisplayBothClocks();
12264         gameMode = EditGame;
12265         ModeHighlight();
12266         gameFileFP = NULL;
12267         cmailOldMove = 0;
12268         return TRUE;
12269     }
12270
12271     // [HGM] PV info: routine tests if comment empty
12272     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12273         DisplayComment(currentMove - 1, commentList[currentMove]);
12274     }
12275     if (!matchMode && appData.timeDelay != 0)
12276       DrawPosition(FALSE, boards[currentMove]);
12277
12278     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12279       programStats.ok_to_send = 1;
12280     }
12281
12282     /* if the first token after the PGN tags is a move
12283      * and not move number 1, retrieve it from the parser
12284      */
12285     if (cm != MoveNumberOne)
12286         LoadGameOneMove(cm);
12287
12288     /* load the remaining moves from the file */
12289     while (LoadGameOneMove(EndOfFile)) {
12290       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12291       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12292     }
12293
12294     /* rewind to the start of the game */
12295     currentMove = backwardMostMove;
12296
12297     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12298
12299     if (oldGameMode == AnalyzeFile ||
12300         oldGameMode == AnalyzeMode) {
12301       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12302       keepInfo = 1;
12303       AnalyzeFileEvent();
12304       keepInfo = 0;
12305     }
12306
12307     if (!matchMode && pos > 0) {
12308         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12309     } else
12310     if (matchMode || appData.timeDelay == 0) {
12311       ToEndEvent();
12312     } else if (appData.timeDelay > 0) {
12313       AutoPlayGameLoop();
12314     }
12315
12316     if (appData.debugMode)
12317         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12318
12319     loadFlag = 0; /* [HGM] true game starts */
12320     return TRUE;
12321 }
12322
12323 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12324 int
12325 ReloadPosition (int offset)
12326 {
12327     int positionNumber = lastLoadPositionNumber + offset;
12328     if (lastLoadPositionFP == NULL) {
12329         DisplayError(_("No position has been loaded yet"), 0);
12330         return FALSE;
12331     }
12332     if (positionNumber <= 0) {
12333         DisplayError(_("Can't back up any further"), 0);
12334         return FALSE;
12335     }
12336     return LoadPosition(lastLoadPositionFP, positionNumber,
12337                         lastLoadPositionTitle);
12338 }
12339
12340 /* Load the nth position from the given file */
12341 int
12342 LoadPositionFromFile (char *filename, int n, char *title)
12343 {
12344     FILE *f;
12345     char buf[MSG_SIZ];
12346
12347     if (strcmp(filename, "-") == 0) {
12348         return LoadPosition(stdin, n, "stdin");
12349     } else {
12350         f = fopen(filename, "rb");
12351         if (f == NULL) {
12352             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12353             DisplayError(buf, errno);
12354             return FALSE;
12355         } else {
12356             return LoadPosition(f, n, title);
12357         }
12358     }
12359 }
12360
12361 /* Load the nth position from the given open file, and close it */
12362 int
12363 LoadPosition (FILE *f, int positionNumber, char *title)
12364 {
12365     char *p, line[MSG_SIZ];
12366     Board initial_position;
12367     int i, j, fenMode, pn;
12368
12369     if (gameMode == Training )
12370         SetTrainingModeOff();
12371
12372     if (gameMode != BeginningOfGame) {
12373         Reset(FALSE, TRUE);
12374     }
12375     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12376         fclose(lastLoadPositionFP);
12377     }
12378     if (positionNumber == 0) positionNumber = 1;
12379     lastLoadPositionFP = f;
12380     lastLoadPositionNumber = positionNumber;
12381     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12382     if (first.pr == NoProc && !appData.noChessProgram) {
12383       StartChessProgram(&first);
12384       InitChessProgram(&first, FALSE);
12385     }
12386     pn = positionNumber;
12387     if (positionNumber < 0) {
12388         /* Negative position number means to seek to that byte offset */
12389         if (fseek(f, -positionNumber, 0) == -1) {
12390             DisplayError(_("Can't seek on position file"), 0);
12391             return FALSE;
12392         };
12393         pn = 1;
12394     } else {
12395         if (fseek(f, 0, 0) == -1) {
12396             if (f == lastLoadPositionFP ?
12397                 positionNumber == lastLoadPositionNumber + 1 :
12398                 positionNumber == 1) {
12399                 pn = 1;
12400             } else {
12401                 DisplayError(_("Can't seek on position file"), 0);
12402                 return FALSE;
12403             }
12404         }
12405     }
12406     /* See if this file is FEN or old-style xboard */
12407     if (fgets(line, MSG_SIZ, f) == NULL) {
12408         DisplayError(_("Position not found in file"), 0);
12409         return FALSE;
12410     }
12411     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12412     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12413
12414     if (pn >= 2) {
12415         if (fenMode || line[0] == '#') pn--;
12416         while (pn > 0) {
12417             /* skip positions before number pn */
12418             if (fgets(line, MSG_SIZ, f) == NULL) {
12419                 Reset(TRUE, TRUE);
12420                 DisplayError(_("Position not found in file"), 0);
12421                 return FALSE;
12422             }
12423             if (fenMode || line[0] == '#') pn--;
12424         }
12425     }
12426
12427     if (fenMode) {
12428         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12429             DisplayError(_("Bad FEN position in file"), 0);
12430             return FALSE;
12431         }
12432     } else {
12433         (void) fgets(line, MSG_SIZ, f);
12434         (void) fgets(line, MSG_SIZ, f);
12435
12436         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12437             (void) fgets(line, MSG_SIZ, f);
12438             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12439                 if (*p == ' ')
12440                   continue;
12441                 initial_position[i][j++] = CharToPiece(*p);
12442             }
12443         }
12444
12445         blackPlaysFirst = FALSE;
12446         if (!feof(f)) {
12447             (void) fgets(line, MSG_SIZ, f);
12448             if (strncmp(line, "black", strlen("black"))==0)
12449               blackPlaysFirst = TRUE;
12450         }
12451     }
12452     startedFromSetupPosition = TRUE;
12453
12454     CopyBoard(boards[0], initial_position);
12455     if (blackPlaysFirst) {
12456         currentMove = forwardMostMove = backwardMostMove = 1;
12457         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12458         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12459         CopyBoard(boards[1], initial_position);
12460         DisplayMessage("", _("Black to play"));
12461     } else {
12462         currentMove = forwardMostMove = backwardMostMove = 0;
12463         DisplayMessage("", _("White to play"));
12464     }
12465     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12466     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12467         SendToProgram("force\n", &first);
12468         SendBoard(&first, forwardMostMove);
12469     }
12470     if (appData.debugMode) {
12471 int i, j;
12472   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12473   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12474         fprintf(debugFP, "Load Position\n");
12475     }
12476
12477     if (positionNumber > 1) {
12478       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12479         DisplayTitle(line);
12480     } else {
12481         DisplayTitle(title);
12482     }
12483     gameMode = EditGame;
12484     ModeHighlight();
12485     ResetClocks();
12486     timeRemaining[0][1] = whiteTimeRemaining;
12487     timeRemaining[1][1] = blackTimeRemaining;
12488     DrawPosition(FALSE, boards[currentMove]);
12489
12490     return TRUE;
12491 }
12492
12493
12494 void
12495 CopyPlayerNameIntoFileName (char **dest, char *src)
12496 {
12497     while (*src != NULLCHAR && *src != ',') {
12498         if (*src == ' ') {
12499             *(*dest)++ = '_';
12500             src++;
12501         } else {
12502             *(*dest)++ = *src++;
12503         }
12504     }
12505 }
12506
12507 char *
12508 DefaultFileName (char *ext)
12509 {
12510     static char def[MSG_SIZ];
12511     char *p;
12512
12513     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12514         p = def;
12515         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12516         *p++ = '-';
12517         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12518         *p++ = '.';
12519         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12520     } else {
12521         def[0] = NULLCHAR;
12522     }
12523     return def;
12524 }
12525
12526 /* Save the current game to the given file */
12527 int
12528 SaveGameToFile (char *filename, int append)
12529 {
12530     FILE *f;
12531     char buf[MSG_SIZ];
12532     int result, i, t,tot=0;
12533
12534     if (strcmp(filename, "-") == 0) {
12535         return SaveGame(stdout, 0, NULL);
12536     } else {
12537         for(i=0; i<10; i++) { // upto 10 tries
12538              f = fopen(filename, append ? "a" : "w");
12539              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12540              if(f || errno != 13) break;
12541              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12542              tot += t;
12543         }
12544         if (f == NULL) {
12545             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12546             DisplayError(buf, errno);
12547             return FALSE;
12548         } else {
12549             safeStrCpy(buf, lastMsg, MSG_SIZ);
12550             DisplayMessage(_("Waiting for access to save file"), "");
12551             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12552             DisplayMessage(_("Saving game"), "");
12553             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12554             result = SaveGame(f, 0, NULL);
12555             DisplayMessage(buf, "");
12556             return result;
12557         }
12558     }
12559 }
12560
12561 char *
12562 SavePart (char *str)
12563 {
12564     static char buf[MSG_SIZ];
12565     char *p;
12566
12567     p = strchr(str, ' ');
12568     if (p == NULL) return str;
12569     strncpy(buf, str, p - str);
12570     buf[p - str] = NULLCHAR;
12571     return buf;
12572 }
12573
12574 #define PGN_MAX_LINE 75
12575
12576 #define PGN_SIDE_WHITE  0
12577 #define PGN_SIDE_BLACK  1
12578
12579 static int
12580 FindFirstMoveOutOfBook (int side)
12581 {
12582     int result = -1;
12583
12584     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12585         int index = backwardMostMove;
12586         int has_book_hit = 0;
12587
12588         if( (index % 2) != side ) {
12589             index++;
12590         }
12591
12592         while( index < forwardMostMove ) {
12593             /* Check to see if engine is in book */
12594             int depth = pvInfoList[index].depth;
12595             int score = pvInfoList[index].score;
12596             int in_book = 0;
12597
12598             if( depth <= 2 ) {
12599                 in_book = 1;
12600             }
12601             else if( score == 0 && depth == 63 ) {
12602                 in_book = 1; /* Zappa */
12603             }
12604             else if( score == 2 && depth == 99 ) {
12605                 in_book = 1; /* Abrok */
12606             }
12607
12608             has_book_hit += in_book;
12609
12610             if( ! in_book ) {
12611                 result = index;
12612
12613                 break;
12614             }
12615
12616             index += 2;
12617         }
12618     }
12619
12620     return result;
12621 }
12622
12623 void
12624 GetOutOfBookInfo (char * buf)
12625 {
12626     int oob[2];
12627     int i;
12628     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12629
12630     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12631     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12632
12633     *buf = '\0';
12634
12635     if( oob[0] >= 0 || oob[1] >= 0 ) {
12636         for( i=0; i<2; i++ ) {
12637             int idx = oob[i];
12638
12639             if( idx >= 0 ) {
12640                 if( i > 0 && oob[0] >= 0 ) {
12641                     strcat( buf, "   " );
12642                 }
12643
12644                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12645                 sprintf( buf+strlen(buf), "%s%.2f",
12646                     pvInfoList[idx].score >= 0 ? "+" : "",
12647                     pvInfoList[idx].score / 100.0 );
12648             }
12649         }
12650     }
12651 }
12652
12653 /* Save game in PGN style and close the file */
12654 int
12655 SaveGamePGN (FILE *f)
12656 {
12657     int i, offset, linelen, newblock;
12658 //    char *movetext;
12659     char numtext[32];
12660     int movelen, numlen, blank;
12661     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12662
12663     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12664
12665     PrintPGNTags(f, &gameInfo);
12666
12667     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12668
12669     if (backwardMostMove > 0 || startedFromSetupPosition) {
12670         char *fen = PositionToFEN(backwardMostMove, NULL);
12671         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12672         fprintf(f, "\n{--------------\n");
12673         PrintPosition(f, backwardMostMove);
12674         fprintf(f, "--------------}\n");
12675         free(fen);
12676     }
12677     else {
12678         /* [AS] Out of book annotation */
12679         if( appData.saveOutOfBookInfo ) {
12680             char buf[64];
12681
12682             GetOutOfBookInfo( buf );
12683
12684             if( buf[0] != '\0' ) {
12685                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12686             }
12687         }
12688
12689         fprintf(f, "\n");
12690     }
12691
12692     i = backwardMostMove;
12693     linelen = 0;
12694     newblock = TRUE;
12695
12696     while (i < forwardMostMove) {
12697         /* Print comments preceding this move */
12698         if (commentList[i] != NULL) {
12699             if (linelen > 0) fprintf(f, "\n");
12700             fprintf(f, "%s", commentList[i]);
12701             linelen = 0;
12702             newblock = TRUE;
12703         }
12704
12705         /* Format move number */
12706         if ((i % 2) == 0)
12707           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12708         else
12709           if (newblock)
12710             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12711           else
12712             numtext[0] = NULLCHAR;
12713
12714         numlen = strlen(numtext);
12715         newblock = FALSE;
12716
12717         /* Print move number */
12718         blank = linelen > 0 && numlen > 0;
12719         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12720             fprintf(f, "\n");
12721             linelen = 0;
12722             blank = 0;
12723         }
12724         if (blank) {
12725             fprintf(f, " ");
12726             linelen++;
12727         }
12728         fprintf(f, "%s", numtext);
12729         linelen += numlen;
12730
12731         /* Get move */
12732         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12733         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12734
12735         /* Print move */
12736         blank = linelen > 0 && movelen > 0;
12737         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12738             fprintf(f, "\n");
12739             linelen = 0;
12740             blank = 0;
12741         }
12742         if (blank) {
12743             fprintf(f, " ");
12744             linelen++;
12745         }
12746         fprintf(f, "%s", move_buffer);
12747         linelen += movelen;
12748
12749         /* [AS] Add PV info if present */
12750         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12751             /* [HGM] add time */
12752             char buf[MSG_SIZ]; int seconds;
12753
12754             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12755
12756             if( seconds <= 0)
12757               buf[0] = 0;
12758             else
12759               if( seconds < 30 )
12760                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12761               else
12762                 {
12763                   seconds = (seconds + 4)/10; // round to full seconds
12764                   if( seconds < 60 )
12765                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12766                   else
12767                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12768                 }
12769
12770             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12771                       pvInfoList[i].score >= 0 ? "+" : "",
12772                       pvInfoList[i].score / 100.0,
12773                       pvInfoList[i].depth,
12774                       buf );
12775
12776             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12777
12778             /* Print score/depth */
12779             blank = linelen > 0 && movelen > 0;
12780             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12781                 fprintf(f, "\n");
12782                 linelen = 0;
12783                 blank = 0;
12784             }
12785             if (blank) {
12786                 fprintf(f, " ");
12787                 linelen++;
12788             }
12789             fprintf(f, "%s", move_buffer);
12790             linelen += movelen;
12791         }
12792
12793         i++;
12794     }
12795
12796     /* Start a new line */
12797     if (linelen > 0) fprintf(f, "\n");
12798
12799     /* Print comments after last move */
12800     if (commentList[i] != NULL) {
12801         fprintf(f, "%s\n", commentList[i]);
12802     }
12803
12804     /* Print result */
12805     if (gameInfo.resultDetails != NULL &&
12806         gameInfo.resultDetails[0] != NULLCHAR) {
12807         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12808                 PGNResult(gameInfo.result));
12809     } else {
12810         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12811     }
12812
12813     fclose(f);
12814     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12815     return TRUE;
12816 }
12817
12818 /* Save game in old style and close the file */
12819 int
12820 SaveGameOldStyle (FILE *f)
12821 {
12822     int i, offset;
12823     time_t tm;
12824
12825     tm = time((time_t *) NULL);
12826
12827     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12828     PrintOpponents(f);
12829
12830     if (backwardMostMove > 0 || startedFromSetupPosition) {
12831         fprintf(f, "\n[--------------\n");
12832         PrintPosition(f, backwardMostMove);
12833         fprintf(f, "--------------]\n");
12834     } else {
12835         fprintf(f, "\n");
12836     }
12837
12838     i = backwardMostMove;
12839     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12840
12841     while (i < forwardMostMove) {
12842         if (commentList[i] != NULL) {
12843             fprintf(f, "[%s]\n", commentList[i]);
12844         }
12845
12846         if ((i % 2) == 1) {
12847             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12848             i++;
12849         } else {
12850             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12851             i++;
12852             if (commentList[i] != NULL) {
12853                 fprintf(f, "\n");
12854                 continue;
12855             }
12856             if (i >= forwardMostMove) {
12857                 fprintf(f, "\n");
12858                 break;
12859             }
12860             fprintf(f, "%s\n", parseList[i]);
12861             i++;
12862         }
12863     }
12864
12865     if (commentList[i] != NULL) {
12866         fprintf(f, "[%s]\n", commentList[i]);
12867     }
12868
12869     /* This isn't really the old style, but it's close enough */
12870     if (gameInfo.resultDetails != NULL &&
12871         gameInfo.resultDetails[0] != NULLCHAR) {
12872         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12873                 gameInfo.resultDetails);
12874     } else {
12875         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12876     }
12877
12878     fclose(f);
12879     return TRUE;
12880 }
12881
12882 /* Save the current game to open file f and close the file */
12883 int
12884 SaveGame (FILE *f, int dummy, char *dummy2)
12885 {
12886     if (gameMode == EditPosition) EditPositionDone(TRUE);
12887     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12888     if (appData.oldSaveStyle)
12889       return SaveGameOldStyle(f);
12890     else
12891       return SaveGamePGN(f);
12892 }
12893
12894 /* Save the current position to the given file */
12895 int
12896 SavePositionToFile (char *filename)
12897 {
12898     FILE *f;
12899     char buf[MSG_SIZ];
12900
12901     if (strcmp(filename, "-") == 0) {
12902         return SavePosition(stdout, 0, NULL);
12903     } else {
12904         f = fopen(filename, "a");
12905         if (f == NULL) {
12906             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12907             DisplayError(buf, errno);
12908             return FALSE;
12909         } else {
12910             safeStrCpy(buf, lastMsg, MSG_SIZ);
12911             DisplayMessage(_("Waiting for access to save file"), "");
12912             flock(fileno(f), LOCK_EX); // [HGM] lock
12913             DisplayMessage(_("Saving position"), "");
12914             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12915             SavePosition(f, 0, NULL);
12916             DisplayMessage(buf, "");
12917             return TRUE;
12918         }
12919     }
12920 }
12921
12922 /* Save the current position to the given open file and close the file */
12923 int
12924 SavePosition (FILE *f, int dummy, char *dummy2)
12925 {
12926     time_t tm;
12927     char *fen;
12928
12929     if (gameMode == EditPosition) EditPositionDone(TRUE);
12930     if (appData.oldSaveStyle) {
12931         tm = time((time_t *) NULL);
12932
12933         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12934         PrintOpponents(f);
12935         fprintf(f, "[--------------\n");
12936         PrintPosition(f, currentMove);
12937         fprintf(f, "--------------]\n");
12938     } else {
12939         fen = PositionToFEN(currentMove, NULL);
12940         fprintf(f, "%s\n", fen);
12941         free(fen);
12942     }
12943     fclose(f);
12944     return TRUE;
12945 }
12946
12947 void
12948 ReloadCmailMsgEvent (int unregister)
12949 {
12950 #if !WIN32
12951     static char *inFilename = NULL;
12952     static char *outFilename;
12953     int i;
12954     struct stat inbuf, outbuf;
12955     int status;
12956
12957     /* Any registered moves are unregistered if unregister is set, */
12958     /* i.e. invoked by the signal handler */
12959     if (unregister) {
12960         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12961             cmailMoveRegistered[i] = FALSE;
12962             if (cmailCommentList[i] != NULL) {
12963                 free(cmailCommentList[i]);
12964                 cmailCommentList[i] = NULL;
12965             }
12966         }
12967         nCmailMovesRegistered = 0;
12968     }
12969
12970     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12971         cmailResult[i] = CMAIL_NOT_RESULT;
12972     }
12973     nCmailResults = 0;
12974
12975     if (inFilename == NULL) {
12976         /* Because the filenames are static they only get malloced once  */
12977         /* and they never get freed                                      */
12978         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12979         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12980
12981         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12982         sprintf(outFilename, "%s.out", appData.cmailGameName);
12983     }
12984
12985     status = stat(outFilename, &outbuf);
12986     if (status < 0) {
12987         cmailMailedMove = FALSE;
12988     } else {
12989         status = stat(inFilename, &inbuf);
12990         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12991     }
12992
12993     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12994        counts the games, notes how each one terminated, etc.
12995
12996        It would be nice to remove this kludge and instead gather all
12997        the information while building the game list.  (And to keep it
12998        in the game list nodes instead of having a bunch of fixed-size
12999        parallel arrays.)  Note this will require getting each game's
13000        termination from the PGN tags, as the game list builder does
13001        not process the game moves.  --mann
13002        */
13003     cmailMsgLoaded = TRUE;
13004     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13005
13006     /* Load first game in the file or popup game menu */
13007     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13008
13009 #endif /* !WIN32 */
13010     return;
13011 }
13012
13013 int
13014 RegisterMove ()
13015 {
13016     FILE *f;
13017     char string[MSG_SIZ];
13018
13019     if (   cmailMailedMove
13020         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13021         return TRUE;            /* Allow free viewing  */
13022     }
13023
13024     /* Unregister move to ensure that we don't leave RegisterMove        */
13025     /* with the move registered when the conditions for registering no   */
13026     /* longer hold                                                       */
13027     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13028         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13029         nCmailMovesRegistered --;
13030
13031         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13032           {
13033               free(cmailCommentList[lastLoadGameNumber - 1]);
13034               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13035           }
13036     }
13037
13038     if (cmailOldMove == -1) {
13039         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13040         return FALSE;
13041     }
13042
13043     if (currentMove > cmailOldMove + 1) {
13044         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13045         return FALSE;
13046     }
13047
13048     if (currentMove < cmailOldMove) {
13049         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13050         return FALSE;
13051     }
13052
13053     if (forwardMostMove > currentMove) {
13054         /* Silently truncate extra moves */
13055         TruncateGame();
13056     }
13057
13058     if (   (currentMove == cmailOldMove + 1)
13059         || (   (currentMove == cmailOldMove)
13060             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13061                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13062         if (gameInfo.result != GameUnfinished) {
13063             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13064         }
13065
13066         if (commentList[currentMove] != NULL) {
13067             cmailCommentList[lastLoadGameNumber - 1]
13068               = StrSave(commentList[currentMove]);
13069         }
13070         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13071
13072         if (appData.debugMode)
13073           fprintf(debugFP, "Saving %s for game %d\n",
13074                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13075
13076         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13077
13078         f = fopen(string, "w");
13079         if (appData.oldSaveStyle) {
13080             SaveGameOldStyle(f); /* also closes the file */
13081
13082             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13083             f = fopen(string, "w");
13084             SavePosition(f, 0, NULL); /* also closes the file */
13085         } else {
13086             fprintf(f, "{--------------\n");
13087             PrintPosition(f, currentMove);
13088             fprintf(f, "--------------}\n\n");
13089
13090             SaveGame(f, 0, NULL); /* also closes the file*/
13091         }
13092
13093         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13094         nCmailMovesRegistered ++;
13095     } else if (nCmailGames == 1) {
13096         DisplayError(_("You have not made a move yet"), 0);
13097         return FALSE;
13098     }
13099
13100     return TRUE;
13101 }
13102
13103 void
13104 MailMoveEvent ()
13105 {
13106 #if !WIN32
13107     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13108     FILE *commandOutput;
13109     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13110     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13111     int nBuffers;
13112     int i;
13113     int archived;
13114     char *arcDir;
13115
13116     if (! cmailMsgLoaded) {
13117         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13118         return;
13119     }
13120
13121     if (nCmailGames == nCmailResults) {
13122         DisplayError(_("No unfinished games"), 0);
13123         return;
13124     }
13125
13126 #if CMAIL_PROHIBIT_REMAIL
13127     if (cmailMailedMove) {
13128       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);
13129         DisplayError(msg, 0);
13130         return;
13131     }
13132 #endif
13133
13134     if (! (cmailMailedMove || RegisterMove())) return;
13135
13136     if (   cmailMailedMove
13137         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13138       snprintf(string, MSG_SIZ, partCommandString,
13139                appData.debugMode ? " -v" : "", appData.cmailGameName);
13140         commandOutput = popen(string, "r");
13141
13142         if (commandOutput == NULL) {
13143             DisplayError(_("Failed to invoke cmail"), 0);
13144         } else {
13145             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13146                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13147             }
13148             if (nBuffers > 1) {
13149                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13150                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13151                 nBytes = MSG_SIZ - 1;
13152             } else {
13153                 (void) memcpy(msg, buffer, nBytes);
13154             }
13155             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13156
13157             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13158                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13159
13160                 archived = TRUE;
13161                 for (i = 0; i < nCmailGames; i ++) {
13162                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13163                         archived = FALSE;
13164                     }
13165                 }
13166                 if (   archived
13167                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13168                         != NULL)) {
13169                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13170                            arcDir,
13171                            appData.cmailGameName,
13172                            gameInfo.date);
13173                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13174                     cmailMsgLoaded = FALSE;
13175                 }
13176             }
13177
13178             DisplayInformation(msg);
13179             pclose(commandOutput);
13180         }
13181     } else {
13182         if ((*cmailMsg) != '\0') {
13183             DisplayInformation(cmailMsg);
13184         }
13185     }
13186
13187     return;
13188 #endif /* !WIN32 */
13189 }
13190
13191 char *
13192 CmailMsg ()
13193 {
13194 #if WIN32
13195     return NULL;
13196 #else
13197     int  prependComma = 0;
13198     char number[5];
13199     char string[MSG_SIZ];       /* Space for game-list */
13200     int  i;
13201
13202     if (!cmailMsgLoaded) return "";
13203
13204     if (cmailMailedMove) {
13205       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13206     } else {
13207         /* Create a list of games left */
13208       snprintf(string, MSG_SIZ, "[");
13209         for (i = 0; i < nCmailGames; i ++) {
13210             if (! (   cmailMoveRegistered[i]
13211                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13212                 if (prependComma) {
13213                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13214                 } else {
13215                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13216                     prependComma = 1;
13217                 }
13218
13219                 strcat(string, number);
13220             }
13221         }
13222         strcat(string, "]");
13223
13224         if (nCmailMovesRegistered + nCmailResults == 0) {
13225             switch (nCmailGames) {
13226               case 1:
13227                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13228                 break;
13229
13230               case 2:
13231                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13232                 break;
13233
13234               default:
13235                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13236                          nCmailGames);
13237                 break;
13238             }
13239         } else {
13240             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13241               case 1:
13242                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13243                          string);
13244                 break;
13245
13246               case 0:
13247                 if (nCmailResults == nCmailGames) {
13248                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13249                 } else {
13250                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13251                 }
13252                 break;
13253
13254               default:
13255                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13256                          string);
13257             }
13258         }
13259     }
13260     return cmailMsg;
13261 #endif /* WIN32 */
13262 }
13263
13264 void
13265 ResetGameEvent ()
13266 {
13267     if (gameMode == Training)
13268       SetTrainingModeOff();
13269
13270     Reset(TRUE, TRUE);
13271     cmailMsgLoaded = FALSE;
13272     if (appData.icsActive) {
13273       SendToICS(ics_prefix);
13274       SendToICS("refresh\n");
13275     }
13276 }
13277
13278 void
13279 ExitEvent (int status)
13280 {
13281     exiting++;
13282     if (exiting > 2) {
13283       /* Give up on clean exit */
13284       exit(status);
13285     }
13286     if (exiting > 1) {
13287       /* Keep trying for clean exit */
13288       return;
13289     }
13290
13291     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13292
13293     if (telnetISR != NULL) {
13294       RemoveInputSource(telnetISR);
13295     }
13296     if (icsPR != NoProc) {
13297       DestroyChildProcess(icsPR, TRUE);
13298     }
13299
13300     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13301     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13302
13303     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13304     /* make sure this other one finishes before killing it!                  */
13305     if(endingGame) { int count = 0;
13306         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13307         while(endingGame && count++ < 10) DoSleep(1);
13308         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13309     }
13310
13311     /* Kill off chess programs */
13312     if (first.pr != NoProc) {
13313         ExitAnalyzeMode();
13314
13315         DoSleep( appData.delayBeforeQuit );
13316         SendToProgram("quit\n", &first);
13317         DoSleep( appData.delayAfterQuit );
13318         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13319     }
13320     if (second.pr != NoProc) {
13321         DoSleep( appData.delayBeforeQuit );
13322         SendToProgram("quit\n", &second);
13323         DoSleep( appData.delayAfterQuit );
13324         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13325     }
13326     if (first.isr != NULL) {
13327         RemoveInputSource(first.isr);
13328     }
13329     if (second.isr != NULL) {
13330         RemoveInputSource(second.isr);
13331     }
13332
13333     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13334     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13335
13336     ShutDownFrontEnd();
13337     exit(status);
13338 }
13339
13340 void
13341 PauseEvent ()
13342 {
13343     if (appData.debugMode)
13344         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13345     if (pausing) {
13346         pausing = FALSE;
13347         ModeHighlight();
13348         if (gameMode == MachinePlaysWhite ||
13349             gameMode == MachinePlaysBlack) {
13350             StartClocks();
13351         } else {
13352             DisplayBothClocks();
13353         }
13354         if (gameMode == PlayFromGameFile) {
13355             if (appData.timeDelay >= 0)
13356                 AutoPlayGameLoop();
13357         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13358             Reset(FALSE, TRUE);
13359             SendToICS(ics_prefix);
13360             SendToICS("refresh\n");
13361         } else if (currentMove < forwardMostMove) {
13362             ForwardInner(forwardMostMove);
13363         }
13364         pauseExamInvalid = FALSE;
13365     } else {
13366         switch (gameMode) {
13367           default:
13368             return;
13369           case IcsExamining:
13370             pauseExamForwardMostMove = forwardMostMove;
13371             pauseExamInvalid = FALSE;
13372             /* fall through */
13373           case IcsObserving:
13374           case IcsPlayingWhite:
13375           case IcsPlayingBlack:
13376             pausing = TRUE;
13377             ModeHighlight();
13378             return;
13379           case PlayFromGameFile:
13380             (void) StopLoadGameTimer();
13381             pausing = TRUE;
13382             ModeHighlight();
13383             break;
13384           case BeginningOfGame:
13385             if (appData.icsActive) return;
13386             /* else fall through */
13387           case MachinePlaysWhite:
13388           case MachinePlaysBlack:
13389           case TwoMachinesPlay:
13390             if (forwardMostMove == 0)
13391               return;           /* don't pause if no one has moved */
13392             if ((gameMode == MachinePlaysWhite &&
13393                  !WhiteOnMove(forwardMostMove)) ||
13394                 (gameMode == MachinePlaysBlack &&
13395                  WhiteOnMove(forwardMostMove))) {
13396                 StopClocks();
13397             }
13398           case AnalyzeMode:
13399             pausing = TRUE;
13400             ModeHighlight();
13401             break;
13402         }
13403     }
13404 }
13405
13406 void
13407 EditCommentEvent ()
13408 {
13409     char title[MSG_SIZ];
13410
13411     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13412       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13413     } else {
13414       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13415                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13416                parseList[currentMove - 1]);
13417     }
13418
13419     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13420 }
13421
13422
13423 void
13424 EditTagsEvent ()
13425 {
13426     char *tags = PGNTags(&gameInfo);
13427     bookUp = FALSE;
13428     EditTagsPopUp(tags, NULL);
13429     free(tags);
13430 }
13431
13432 void
13433 ToggleSecond ()
13434 {
13435   if(second.analyzing) {
13436     SendToProgram("exit\n", &second);
13437     second.analyzing = FALSE;
13438   } else {
13439     if (second.pr == NoProc) StartChessProgram(&second);
13440     InitChessProgram(&second, FALSE);
13441     FeedMovesToProgram(&second, currentMove);
13442
13443     SendToProgram("analyze\n", &second);
13444     second.analyzing = TRUE;
13445   }
13446 }
13447
13448 /* Toggle ShowThinking */
13449 void
13450 ToggleShowThinking()
13451 {
13452   appData.showThinking = !appData.showThinking;
13453   ShowThinkingEvent();
13454 }
13455
13456 int
13457 AnalyzeModeEvent ()
13458 {
13459     char buf[MSG_SIZ];
13460
13461     if (!first.analysisSupport) {
13462       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13463       DisplayError(buf, 0);
13464       return 0;
13465     }
13466     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13467     if (appData.icsActive) {
13468         if (gameMode != IcsObserving) {
13469           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13470             DisplayError(buf, 0);
13471             /* secure check */
13472             if (appData.icsEngineAnalyze) {
13473                 if (appData.debugMode)
13474                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
13475                 ExitAnalyzeMode();
13476                 ModeHighlight();
13477             }
13478             return 0;
13479         }
13480         /* if enable, user wants to disable icsEngineAnalyze */
13481         if (appData.icsEngineAnalyze) {
13482                 ExitAnalyzeMode();
13483                 ModeHighlight();
13484                 return 0;
13485         }
13486         appData.icsEngineAnalyze = TRUE;
13487         if (appData.debugMode)
13488             fprintf(debugFP, _("ICS engine analyze starting... \n"));
13489     }
13490
13491     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
13492     if (appData.noChessProgram || gameMode == AnalyzeMode)
13493       return 0;
13494
13495     if (gameMode != AnalyzeFile) {
13496         if (!appData.icsEngineAnalyze) {
13497                EditGameEvent();
13498                if (gameMode != EditGame) return 0;
13499         }
13500         if (!appData.showThinking) ToggleShowThinking();
13501         ResurrectChessProgram();
13502         SendToProgram("analyze\n", &first);
13503         first.analyzing = TRUE;
13504         /*first.maybeThinking = TRUE;*/
13505         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13506         EngineOutputPopUp();
13507     }
13508     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13509     pausing = FALSE;
13510     ModeHighlight();
13511     SetGameInfo();
13512
13513     StartAnalysisClock();
13514     GetTimeMark(&lastNodeCountTime);
13515     lastNodeCount = 0;
13516     return 1;
13517 }
13518
13519 void
13520 AnalyzeFileEvent ()
13521 {
13522     if (appData.noChessProgram || gameMode == AnalyzeFile)
13523       return;
13524
13525     if (!first.analysisSupport) {
13526       char buf[MSG_SIZ];
13527       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13528       DisplayError(buf, 0);
13529       return;
13530     }
13531
13532     if (gameMode != AnalyzeMode) {
13533         EditGameEvent();
13534         if (gameMode != EditGame) return;
13535         if (!appData.showThinking) ToggleShowThinking();
13536         ResurrectChessProgram();
13537         SendToProgram("analyze\n", &first);
13538         first.analyzing = TRUE;
13539         /*first.maybeThinking = TRUE;*/
13540         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13541         EngineOutputPopUp();
13542     }
13543     gameMode = AnalyzeFile;
13544     pausing = FALSE;
13545     ModeHighlight();
13546     SetGameInfo();
13547
13548     StartAnalysisClock();
13549     GetTimeMark(&lastNodeCountTime);
13550     lastNodeCount = 0;
13551     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13552     AnalysisPeriodicEvent(1);
13553 }
13554
13555 void
13556 MachineWhiteEvent ()
13557 {
13558     char buf[MSG_SIZ];
13559     char *bookHit = NULL;
13560
13561     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13562       return;
13563
13564
13565     if (gameMode == PlayFromGameFile ||
13566         gameMode == TwoMachinesPlay  ||
13567         gameMode == Training         ||
13568         gameMode == AnalyzeMode      ||
13569         gameMode == EndOfGame)
13570         EditGameEvent();
13571
13572     if (gameMode == EditPosition)
13573         EditPositionDone(TRUE);
13574
13575     if (!WhiteOnMove(currentMove)) {
13576         DisplayError(_("It is not White's turn"), 0);
13577         return;
13578     }
13579
13580     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13581       ExitAnalyzeMode();
13582
13583     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13584         gameMode == AnalyzeFile)
13585         TruncateGame();
13586
13587     ResurrectChessProgram();    /* in case it isn't running */
13588     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13589         gameMode = MachinePlaysWhite;
13590         ResetClocks();
13591     } else
13592     gameMode = MachinePlaysWhite;
13593     pausing = FALSE;
13594     ModeHighlight();
13595     SetGameInfo();
13596     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13597     DisplayTitle(buf);
13598     if (first.sendName) {
13599       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13600       SendToProgram(buf, &first);
13601     }
13602     if (first.sendTime) {
13603       if (first.useColors) {
13604         SendToProgram("black\n", &first); /*gnu kludge*/
13605       }
13606       SendTimeRemaining(&first, TRUE);
13607     }
13608     if (first.useColors) {
13609       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13610     }
13611     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13612     SetMachineThinkingEnables();
13613     first.maybeThinking = TRUE;
13614     StartClocks();
13615     firstMove = FALSE;
13616
13617     if (appData.autoFlipView && !flipView) {
13618       flipView = !flipView;
13619       DrawPosition(FALSE, NULL);
13620       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13621     }
13622
13623     if(bookHit) { // [HGM] book: simulate book reply
13624         static char bookMove[MSG_SIZ]; // a bit generous?
13625
13626         programStats.nodes = programStats.depth = programStats.time =
13627         programStats.score = programStats.got_only_move = 0;
13628         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13629
13630         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13631         strcat(bookMove, bookHit);
13632         HandleMachineMove(bookMove, &first);
13633     }
13634 }
13635
13636 void
13637 MachineBlackEvent ()
13638 {
13639   char buf[MSG_SIZ];
13640   char *bookHit = NULL;
13641
13642     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13643         return;
13644
13645
13646     if (gameMode == PlayFromGameFile ||
13647         gameMode == TwoMachinesPlay  ||
13648         gameMode == Training         ||
13649         gameMode == AnalyzeMode      ||
13650         gameMode == EndOfGame)
13651         EditGameEvent();
13652
13653     if (gameMode == EditPosition)
13654         EditPositionDone(TRUE);
13655
13656     if (WhiteOnMove(currentMove)) {
13657         DisplayError(_("It is not Black's turn"), 0);
13658         return;
13659     }
13660
13661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13662       ExitAnalyzeMode();
13663
13664     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13665         gameMode == AnalyzeFile)
13666         TruncateGame();
13667
13668     ResurrectChessProgram();    /* in case it isn't running */
13669     gameMode = MachinePlaysBlack;
13670     pausing = FALSE;
13671     ModeHighlight();
13672     SetGameInfo();
13673     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13674     DisplayTitle(buf);
13675     if (first.sendName) {
13676       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13677       SendToProgram(buf, &first);
13678     }
13679     if (first.sendTime) {
13680       if (first.useColors) {
13681         SendToProgram("white\n", &first); /*gnu kludge*/
13682       }
13683       SendTimeRemaining(&first, FALSE);
13684     }
13685     if (first.useColors) {
13686       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13687     }
13688     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13689     SetMachineThinkingEnables();
13690     first.maybeThinking = TRUE;
13691     StartClocks();
13692
13693     if (appData.autoFlipView && flipView) {
13694       flipView = !flipView;
13695       DrawPosition(FALSE, NULL);
13696       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13697     }
13698     if(bookHit) { // [HGM] book: simulate book reply
13699         static char bookMove[MSG_SIZ]; // a bit generous?
13700
13701         programStats.nodes = programStats.depth = programStats.time =
13702         programStats.score = programStats.got_only_move = 0;
13703         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13704
13705         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13706         strcat(bookMove, bookHit);
13707         HandleMachineMove(bookMove, &first);
13708     }
13709 }
13710
13711
13712 void
13713 DisplayTwoMachinesTitle ()
13714 {
13715     char buf[MSG_SIZ];
13716     if (appData.matchGames > 0) {
13717         if(appData.tourneyFile[0]) {
13718           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13719                    gameInfo.white, _("vs."), gameInfo.black,
13720                    nextGame+1, appData.matchGames+1,
13721                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13722         } else 
13723         if (first.twoMachinesColor[0] == 'w') {
13724           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13725                    gameInfo.white, _("vs."),  gameInfo.black,
13726                    first.matchWins, second.matchWins,
13727                    matchGame - 1 - (first.matchWins + second.matchWins));
13728         } else {
13729           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13730                    gameInfo.white, _("vs."), gameInfo.black,
13731                    second.matchWins, first.matchWins,
13732                    matchGame - 1 - (first.matchWins + second.matchWins));
13733         }
13734     } else {
13735       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13736     }
13737     DisplayTitle(buf);
13738 }
13739
13740 void
13741 SettingsMenuIfReady ()
13742 {
13743   if (second.lastPing != second.lastPong) {
13744     DisplayMessage("", _("Waiting for second chess program"));
13745     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13746     return;
13747   }
13748   ThawUI();
13749   DisplayMessage("", "");
13750   SettingsPopUp(&second);
13751 }
13752
13753 int
13754 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13755 {
13756     char buf[MSG_SIZ];
13757     if (cps->pr == NoProc) {
13758         StartChessProgram(cps);
13759         if (cps->protocolVersion == 1) {
13760           retry();
13761         } else {
13762           /* kludge: allow timeout for initial "feature" command */
13763           FreezeUI();
13764           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13765           DisplayMessage("", buf);
13766           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13767         }
13768         return 1;
13769     }
13770     return 0;
13771 }
13772
13773 void
13774 TwoMachinesEvent P((void))
13775 {
13776     int i;
13777     char buf[MSG_SIZ];
13778     ChessProgramState *onmove;
13779     char *bookHit = NULL;
13780     static int stalling = 0;
13781     TimeMark now;
13782     long wait;
13783
13784     if (appData.noChessProgram) return;
13785
13786     switch (gameMode) {
13787       case TwoMachinesPlay:
13788         return;
13789       case MachinePlaysWhite:
13790       case MachinePlaysBlack:
13791         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13792             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13793             return;
13794         }
13795         /* fall through */
13796       case BeginningOfGame:
13797       case PlayFromGameFile:
13798       case EndOfGame:
13799         EditGameEvent();
13800         if (gameMode != EditGame) return;
13801         break;
13802       case EditPosition:
13803         EditPositionDone(TRUE);
13804         break;
13805       case AnalyzeMode:
13806       case AnalyzeFile:
13807         ExitAnalyzeMode();
13808         break;
13809       case EditGame:
13810       default:
13811         break;
13812     }
13813
13814 //    forwardMostMove = currentMove;
13815     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13816
13817     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13818
13819     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13820     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13821       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13822       return;
13823     }
13824
13825     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13826         DisplayError("second engine does not play this", 0);
13827         return;
13828     }
13829
13830     if(!stalling) {
13831       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13832       SendToProgram("force\n", &second);
13833       stalling = 1;
13834       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13835       return;
13836     }
13837     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13838     if(appData.matchPause>10000 || appData.matchPause<10)
13839                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13840     wait = SubtractTimeMarks(&now, &pauseStart);
13841     if(wait < appData.matchPause) {
13842         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13843         return;
13844     }
13845     // we are now committed to starting the game
13846     stalling = 0;
13847     DisplayMessage("", "");
13848     if (startedFromSetupPosition) {
13849         SendBoard(&second, backwardMostMove);
13850     if (appData.debugMode) {
13851         fprintf(debugFP, "Two Machines\n");
13852     }
13853     }
13854     for (i = backwardMostMove; i < forwardMostMove; i++) {
13855         SendMoveToProgram(i, &second);
13856     }
13857
13858     gameMode = TwoMachinesPlay;
13859     pausing = FALSE;
13860     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13861     SetGameInfo();
13862     DisplayTwoMachinesTitle();
13863     firstMove = TRUE;
13864     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13865         onmove = &first;
13866     } else {
13867         onmove = &second;
13868     }
13869     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13870     SendToProgram(first.computerString, &first);
13871     if (first.sendName) {
13872       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13873       SendToProgram(buf, &first);
13874     }
13875     SendToProgram(second.computerString, &second);
13876     if (second.sendName) {
13877       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13878       SendToProgram(buf, &second);
13879     }
13880
13881     ResetClocks();
13882     if (!first.sendTime || !second.sendTime) {
13883         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13884         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13885     }
13886     if (onmove->sendTime) {
13887       if (onmove->useColors) {
13888         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13889       }
13890       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13891     }
13892     if (onmove->useColors) {
13893       SendToProgram(onmove->twoMachinesColor, onmove);
13894     }
13895     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13896 //    SendToProgram("go\n", onmove);
13897     onmove->maybeThinking = TRUE;
13898     SetMachineThinkingEnables();
13899
13900     StartClocks();
13901
13902     if(bookHit) { // [HGM] book: simulate book reply
13903         static char bookMove[MSG_SIZ]; // a bit generous?
13904
13905         programStats.nodes = programStats.depth = programStats.time =
13906         programStats.score = programStats.got_only_move = 0;
13907         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13908
13909         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13910         strcat(bookMove, bookHit);
13911         savedMessage = bookMove; // args for deferred call
13912         savedState = onmove;
13913         ScheduleDelayedEvent(DeferredBookMove, 1);
13914     }
13915 }
13916
13917 void
13918 TrainingEvent ()
13919 {
13920     if (gameMode == Training) {
13921       SetTrainingModeOff();
13922       gameMode = PlayFromGameFile;
13923       DisplayMessage("", _("Training mode off"));
13924     } else {
13925       gameMode = Training;
13926       animateTraining = appData.animate;
13927
13928       /* make sure we are not already at the end of the game */
13929       if (currentMove < forwardMostMove) {
13930         SetTrainingModeOn();
13931         DisplayMessage("", _("Training mode on"));
13932       } else {
13933         gameMode = PlayFromGameFile;
13934         DisplayError(_("Already at end of game"), 0);
13935       }
13936     }
13937     ModeHighlight();
13938 }
13939
13940 void
13941 IcsClientEvent ()
13942 {
13943     if (!appData.icsActive) return;
13944     switch (gameMode) {
13945       case IcsPlayingWhite:
13946       case IcsPlayingBlack:
13947       case IcsObserving:
13948       case IcsIdle:
13949       case BeginningOfGame:
13950       case IcsExamining:
13951         return;
13952
13953       case EditGame:
13954         break;
13955
13956       case EditPosition:
13957         EditPositionDone(TRUE);
13958         break;
13959
13960       case AnalyzeMode:
13961       case AnalyzeFile:
13962         ExitAnalyzeMode();
13963         break;
13964
13965       default:
13966         EditGameEvent();
13967         break;
13968     }
13969
13970     gameMode = IcsIdle;
13971     ModeHighlight();
13972     return;
13973 }
13974
13975 void
13976 EditGameEvent ()
13977 {
13978     int i;
13979
13980     switch (gameMode) {
13981       case Training:
13982         SetTrainingModeOff();
13983         break;
13984       case MachinePlaysWhite:
13985       case MachinePlaysBlack:
13986       case BeginningOfGame:
13987         SendToProgram("force\n", &first);
13988         SetUserThinkingEnables();
13989         break;
13990       case PlayFromGameFile:
13991         (void) StopLoadGameTimer();
13992         if (gameFileFP != NULL) {
13993             gameFileFP = NULL;
13994         }
13995         break;
13996       case EditPosition:
13997         EditPositionDone(TRUE);
13998         break;
13999       case AnalyzeMode:
14000       case AnalyzeFile:
14001         ExitAnalyzeMode();
14002         SendToProgram("force\n", &first);
14003         break;
14004       case TwoMachinesPlay:
14005         GameEnds(EndOfFile, NULL, GE_PLAYER);
14006         ResurrectChessProgram();
14007         SetUserThinkingEnables();
14008         break;
14009       case EndOfGame:
14010         ResurrectChessProgram();
14011         break;
14012       case IcsPlayingBlack:
14013       case IcsPlayingWhite:
14014         DisplayError(_("Warning: You are still playing a game"), 0);
14015         break;
14016       case IcsObserving:
14017         DisplayError(_("Warning: You are still observing a game"), 0);
14018         break;
14019       case IcsExamining:
14020         DisplayError(_("Warning: You are still examining a game"), 0);
14021         break;
14022       case IcsIdle:
14023         break;
14024       case EditGame:
14025       default:
14026         return;
14027     }
14028
14029     pausing = FALSE;
14030     StopClocks();
14031     first.offeredDraw = second.offeredDraw = 0;
14032
14033     if (gameMode == PlayFromGameFile) {
14034         whiteTimeRemaining = timeRemaining[0][currentMove];
14035         blackTimeRemaining = timeRemaining[1][currentMove];
14036         DisplayTitle("");
14037     }
14038
14039     if (gameMode == MachinePlaysWhite ||
14040         gameMode == MachinePlaysBlack ||
14041         gameMode == TwoMachinesPlay ||
14042         gameMode == EndOfGame) {
14043         i = forwardMostMove;
14044         while (i > currentMove) {
14045             SendToProgram("undo\n", &first);
14046             i--;
14047         }
14048         if(!adjustedClock) {
14049         whiteTimeRemaining = timeRemaining[0][currentMove];
14050         blackTimeRemaining = timeRemaining[1][currentMove];
14051         DisplayBothClocks();
14052         }
14053         if (whiteFlag || blackFlag) {
14054             whiteFlag = blackFlag = 0;
14055         }
14056         DisplayTitle("");
14057     }
14058
14059     gameMode = EditGame;
14060     ModeHighlight();
14061     SetGameInfo();
14062 }
14063
14064
14065 void
14066 EditPositionEvent ()
14067 {
14068     if (gameMode == EditPosition) {
14069         EditGameEvent();
14070         return;
14071     }
14072
14073     EditGameEvent();
14074     if (gameMode != EditGame) return;
14075
14076     gameMode = EditPosition;
14077     ModeHighlight();
14078     SetGameInfo();
14079     if (currentMove > 0)
14080       CopyBoard(boards[0], boards[currentMove]);
14081
14082     blackPlaysFirst = !WhiteOnMove(currentMove);
14083     ResetClocks();
14084     currentMove = forwardMostMove = backwardMostMove = 0;
14085     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14086     DisplayMove(-1);
14087     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14088 }
14089
14090 void
14091 ExitAnalyzeMode ()
14092 {
14093     /* [DM] icsEngineAnalyze - possible call from other functions */
14094     if (appData.icsEngineAnalyze) {
14095         appData.icsEngineAnalyze = FALSE;
14096
14097         DisplayMessage("",_("Close ICS engine analyze..."));
14098     }
14099     if (first.analysisSupport && first.analyzing) {
14100       SendToBoth("exit\n");
14101       first.analyzing = second.analyzing = FALSE;
14102     }
14103     thinkOutput[0] = NULLCHAR;
14104 }
14105
14106 void
14107 EditPositionDone (Boolean fakeRights)
14108 {
14109     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14110
14111     startedFromSetupPosition = TRUE;
14112     InitChessProgram(&first, FALSE);
14113     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14114       boards[0][EP_STATUS] = EP_NONE;
14115       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14116       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14117         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14118         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14119       } else boards[0][CASTLING][2] = NoRights;
14120       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14121         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14122         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14123       } else boards[0][CASTLING][5] = NoRights;
14124       if(gameInfo.variant == VariantSChess) {
14125         int i;
14126         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14127           boards[0][VIRGIN][i] = 0;
14128           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14129           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14130         }
14131       }
14132     }
14133     SendToProgram("force\n", &first);
14134     if (blackPlaysFirst) {
14135         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14136         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14137         currentMove = forwardMostMove = backwardMostMove = 1;
14138         CopyBoard(boards[1], boards[0]);
14139     } else {
14140         currentMove = forwardMostMove = backwardMostMove = 0;
14141     }
14142     SendBoard(&first, forwardMostMove);
14143     if (appData.debugMode) {
14144         fprintf(debugFP, "EditPosDone\n");
14145     }
14146     DisplayTitle("");
14147     DisplayMessage("", "");
14148     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14149     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14150     gameMode = EditGame;
14151     ModeHighlight();
14152     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14153     ClearHighlights(); /* [AS] */
14154 }
14155
14156 /* Pause for `ms' milliseconds */
14157 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14158 void
14159 TimeDelay (long ms)
14160 {
14161     TimeMark m1, m2;
14162
14163     GetTimeMark(&m1);
14164     do {
14165         GetTimeMark(&m2);
14166     } while (SubtractTimeMarks(&m2, &m1) < ms);
14167 }
14168
14169 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14170 void
14171 SendMultiLineToICS (char *buf)
14172 {
14173     char temp[MSG_SIZ+1], *p;
14174     int len;
14175
14176     len = strlen(buf);
14177     if (len > MSG_SIZ)
14178       len = MSG_SIZ;
14179
14180     strncpy(temp, buf, len);
14181     temp[len] = 0;
14182
14183     p = temp;
14184     while (*p) {
14185         if (*p == '\n' || *p == '\r')
14186           *p = ' ';
14187         ++p;
14188     }
14189
14190     strcat(temp, "\n");
14191     SendToICS(temp);
14192     SendToPlayer(temp, strlen(temp));
14193 }
14194
14195 void
14196 SetWhiteToPlayEvent ()
14197 {
14198     if (gameMode == EditPosition) {
14199         blackPlaysFirst = FALSE;
14200         DisplayBothClocks();    /* works because currentMove is 0 */
14201     } else if (gameMode == IcsExamining) {
14202         SendToICS(ics_prefix);
14203         SendToICS("tomove white\n");
14204     }
14205 }
14206
14207 void
14208 SetBlackToPlayEvent ()
14209 {
14210     if (gameMode == EditPosition) {
14211         blackPlaysFirst = TRUE;
14212         currentMove = 1;        /* kludge */
14213         DisplayBothClocks();
14214         currentMove = 0;
14215     } else if (gameMode == IcsExamining) {
14216         SendToICS(ics_prefix);
14217         SendToICS("tomove black\n");
14218     }
14219 }
14220
14221 void
14222 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14223 {
14224     char buf[MSG_SIZ];
14225     ChessSquare piece = boards[0][y][x];
14226
14227     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14228
14229     switch (selection) {
14230       case ClearBoard:
14231         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14232             SendToICS(ics_prefix);
14233             SendToICS("bsetup clear\n");
14234         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14235             SendToICS(ics_prefix);
14236             SendToICS("clearboard\n");
14237         } else {
14238             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14239                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14240                 for (y = 0; y < BOARD_HEIGHT; y++) {
14241                     if (gameMode == IcsExamining) {
14242                         if (boards[currentMove][y][x] != EmptySquare) {
14243                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14244                                     AAA + x, ONE + y);
14245                             SendToICS(buf);
14246                         }
14247                     } else {
14248                         boards[0][y][x] = p;
14249                     }
14250                 }
14251             }
14252         }
14253         if (gameMode == EditPosition) {
14254             DrawPosition(FALSE, boards[0]);
14255         }
14256         break;
14257
14258       case WhitePlay:
14259         SetWhiteToPlayEvent();
14260         break;
14261
14262       case BlackPlay:
14263         SetBlackToPlayEvent();
14264         break;
14265
14266       case EmptySquare:
14267         if (gameMode == IcsExamining) {
14268             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14269             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14270             SendToICS(buf);
14271         } else {
14272             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14273                 if(x == BOARD_LEFT-2) {
14274                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14275                     boards[0][y][1] = 0;
14276                 } else
14277                 if(x == BOARD_RGHT+1) {
14278                     if(y >= gameInfo.holdingsSize) break;
14279                     boards[0][y][BOARD_WIDTH-2] = 0;
14280                 } else break;
14281             }
14282             boards[0][y][x] = EmptySquare;
14283             DrawPosition(FALSE, boards[0]);
14284         }
14285         break;
14286
14287       case PromotePiece:
14288         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14289            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14290             selection = (ChessSquare) (PROMOTED piece);
14291         } else if(piece == EmptySquare) selection = WhiteSilver;
14292         else selection = (ChessSquare)((int)piece - 1);
14293         goto defaultlabel;
14294
14295       case DemotePiece:
14296         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14297            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14298             selection = (ChessSquare) (DEMOTED piece);
14299         } else if(piece == EmptySquare) selection = BlackSilver;
14300         else selection = (ChessSquare)((int)piece + 1);
14301         goto defaultlabel;
14302
14303       case WhiteQueen:
14304       case BlackQueen:
14305         if(gameInfo.variant == VariantShatranj ||
14306            gameInfo.variant == VariantXiangqi  ||
14307            gameInfo.variant == VariantCourier  ||
14308            gameInfo.variant == VariantMakruk     )
14309             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14310         goto defaultlabel;
14311
14312       case WhiteKing:
14313       case BlackKing:
14314         if(gameInfo.variant == VariantXiangqi)
14315             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14316         if(gameInfo.variant == VariantKnightmate)
14317             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14318       default:
14319         defaultlabel:
14320         if (gameMode == IcsExamining) {
14321             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14322             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14323                      PieceToChar(selection), AAA + x, ONE + y);
14324             SendToICS(buf);
14325         } else {
14326             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14327                 int n;
14328                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14329                     n = PieceToNumber(selection - BlackPawn);
14330                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14331                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14332                     boards[0][BOARD_HEIGHT-1-n][1]++;
14333                 } else
14334                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14335                     n = PieceToNumber(selection);
14336                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14337                     boards[0][n][BOARD_WIDTH-1] = selection;
14338                     boards[0][n][BOARD_WIDTH-2]++;
14339                 }
14340             } else
14341             boards[0][y][x] = selection;
14342             DrawPosition(TRUE, boards[0]);
14343             ClearHighlights();
14344             fromX = fromY = -1;
14345         }
14346         break;
14347     }
14348 }
14349
14350
14351 void
14352 DropMenuEvent (ChessSquare selection, int x, int y)
14353 {
14354     ChessMove moveType;
14355
14356     switch (gameMode) {
14357       case IcsPlayingWhite:
14358       case MachinePlaysBlack:
14359         if (!WhiteOnMove(currentMove)) {
14360             DisplayMoveError(_("It is Black's turn"));
14361             return;
14362         }
14363         moveType = WhiteDrop;
14364         break;
14365       case IcsPlayingBlack:
14366       case MachinePlaysWhite:
14367         if (WhiteOnMove(currentMove)) {
14368             DisplayMoveError(_("It is White's turn"));
14369             return;
14370         }
14371         moveType = BlackDrop;
14372         break;
14373       case EditGame:
14374         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14375         break;
14376       default:
14377         return;
14378     }
14379
14380     if (moveType == BlackDrop && selection < BlackPawn) {
14381       selection = (ChessSquare) ((int) selection
14382                                  + (int) BlackPawn - (int) WhitePawn);
14383     }
14384     if (boards[currentMove][y][x] != EmptySquare) {
14385         DisplayMoveError(_("That square is occupied"));
14386         return;
14387     }
14388
14389     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14390 }
14391
14392 void
14393 AcceptEvent ()
14394 {
14395     /* Accept a pending offer of any kind from opponent */
14396
14397     if (appData.icsActive) {
14398         SendToICS(ics_prefix);
14399         SendToICS("accept\n");
14400     } else if (cmailMsgLoaded) {
14401         if (currentMove == cmailOldMove &&
14402             commentList[cmailOldMove] != NULL &&
14403             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14404                    "Black offers a draw" : "White offers a draw")) {
14405             TruncateGame();
14406             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14407             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14408         } else {
14409             DisplayError(_("There is no pending offer on this move"), 0);
14410             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14411         }
14412     } else {
14413         /* Not used for offers from chess program */
14414     }
14415 }
14416
14417 void
14418 DeclineEvent ()
14419 {
14420     /* Decline a pending offer of any kind from opponent */
14421
14422     if (appData.icsActive) {
14423         SendToICS(ics_prefix);
14424         SendToICS("decline\n");
14425     } else if (cmailMsgLoaded) {
14426         if (currentMove == cmailOldMove &&
14427             commentList[cmailOldMove] != NULL &&
14428             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14429                    "Black offers a draw" : "White offers a draw")) {
14430 #ifdef NOTDEF
14431             AppendComment(cmailOldMove, "Draw declined", TRUE);
14432             DisplayComment(cmailOldMove - 1, "Draw declined");
14433 #endif /*NOTDEF*/
14434         } else {
14435             DisplayError(_("There is no pending offer on this move"), 0);
14436         }
14437     } else {
14438         /* Not used for offers from chess program */
14439     }
14440 }
14441
14442 void
14443 RematchEvent ()
14444 {
14445     /* Issue ICS rematch command */
14446     if (appData.icsActive) {
14447         SendToICS(ics_prefix);
14448         SendToICS("rematch\n");
14449     }
14450 }
14451
14452 void
14453 CallFlagEvent ()
14454 {
14455     /* Call your opponent's flag (claim a win on time) */
14456     if (appData.icsActive) {
14457         SendToICS(ics_prefix);
14458         SendToICS("flag\n");
14459     } else {
14460         switch (gameMode) {
14461           default:
14462             return;
14463           case MachinePlaysWhite:
14464             if (whiteFlag) {
14465                 if (blackFlag)
14466                   GameEnds(GameIsDrawn, "Both players ran out of time",
14467                            GE_PLAYER);
14468                 else
14469                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14470             } else {
14471                 DisplayError(_("Your opponent is not out of time"), 0);
14472             }
14473             break;
14474           case MachinePlaysBlack:
14475             if (blackFlag) {
14476                 if (whiteFlag)
14477                   GameEnds(GameIsDrawn, "Both players ran out of time",
14478                            GE_PLAYER);
14479                 else
14480                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14481             } else {
14482                 DisplayError(_("Your opponent is not out of time"), 0);
14483             }
14484             break;
14485         }
14486     }
14487 }
14488
14489 void
14490 ClockClick (int which)
14491 {       // [HGM] code moved to back-end from winboard.c
14492         if(which) { // black clock
14493           if (gameMode == EditPosition || gameMode == IcsExamining) {
14494             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14495             SetBlackToPlayEvent();
14496           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14497           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14498           } else if (shiftKey) {
14499             AdjustClock(which, -1);
14500           } else if (gameMode == IcsPlayingWhite ||
14501                      gameMode == MachinePlaysBlack) {
14502             CallFlagEvent();
14503           }
14504         } else { // white clock
14505           if (gameMode == EditPosition || gameMode == IcsExamining) {
14506             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14507             SetWhiteToPlayEvent();
14508           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14509           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14510           } else if (shiftKey) {
14511             AdjustClock(which, -1);
14512           } else if (gameMode == IcsPlayingBlack ||
14513                    gameMode == MachinePlaysWhite) {
14514             CallFlagEvent();
14515           }
14516         }
14517 }
14518
14519 void
14520 DrawEvent ()
14521 {
14522     /* Offer draw or accept pending draw offer from opponent */
14523
14524     if (appData.icsActive) {
14525         /* Note: tournament rules require draw offers to be
14526            made after you make your move but before you punch
14527            your clock.  Currently ICS doesn't let you do that;
14528            instead, you immediately punch your clock after making
14529            a move, but you can offer a draw at any time. */
14530
14531         SendToICS(ics_prefix);
14532         SendToICS("draw\n");
14533         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14534     } else if (cmailMsgLoaded) {
14535         if (currentMove == cmailOldMove &&
14536             commentList[cmailOldMove] != NULL &&
14537             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14538                    "Black offers a draw" : "White offers a draw")) {
14539             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14540             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14541         } else if (currentMove == cmailOldMove + 1) {
14542             char *offer = WhiteOnMove(cmailOldMove) ?
14543               "White offers a draw" : "Black offers a draw";
14544             AppendComment(currentMove, offer, TRUE);
14545             DisplayComment(currentMove - 1, offer);
14546             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14547         } else {
14548             DisplayError(_("You must make your move before offering a draw"), 0);
14549             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14550         }
14551     } else if (first.offeredDraw) {
14552         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14553     } else {
14554         if (first.sendDrawOffers) {
14555             SendToProgram("draw\n", &first);
14556             userOfferedDraw = TRUE;
14557         }
14558     }
14559 }
14560
14561 void
14562 AdjournEvent ()
14563 {
14564     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14565
14566     if (appData.icsActive) {
14567         SendToICS(ics_prefix);
14568         SendToICS("adjourn\n");
14569     } else {
14570         /* Currently GNU Chess doesn't offer or accept Adjourns */
14571     }
14572 }
14573
14574
14575 void
14576 AbortEvent ()
14577 {
14578     /* Offer Abort or accept pending Abort offer from opponent */
14579
14580     if (appData.icsActive) {
14581         SendToICS(ics_prefix);
14582         SendToICS("abort\n");
14583     } else {
14584         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14585     }
14586 }
14587
14588 void
14589 ResignEvent ()
14590 {
14591     /* Resign.  You can do this even if it's not your turn. */
14592
14593     if (appData.icsActive) {
14594         SendToICS(ics_prefix);
14595         SendToICS("resign\n");
14596     } else {
14597         switch (gameMode) {
14598           case MachinePlaysWhite:
14599             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14600             break;
14601           case MachinePlaysBlack:
14602             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14603             break;
14604           case EditGame:
14605             if (cmailMsgLoaded) {
14606                 TruncateGame();
14607                 if (WhiteOnMove(cmailOldMove)) {
14608                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14609                 } else {
14610                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14611                 }
14612                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14613             }
14614             break;
14615           default:
14616             break;
14617         }
14618     }
14619 }
14620
14621
14622 void
14623 StopObservingEvent ()
14624 {
14625     /* Stop observing current games */
14626     SendToICS(ics_prefix);
14627     SendToICS("unobserve\n");
14628 }
14629
14630 void
14631 StopExaminingEvent ()
14632 {
14633     /* Stop observing current game */
14634     SendToICS(ics_prefix);
14635     SendToICS("unexamine\n");
14636 }
14637
14638 void
14639 ForwardInner (int target)
14640 {
14641     int limit; int oldSeekGraphUp = seekGraphUp;
14642
14643     if (appData.debugMode)
14644         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14645                 target, currentMove, forwardMostMove);
14646
14647     if (gameMode == EditPosition)
14648       return;
14649
14650     seekGraphUp = FALSE;
14651     MarkTargetSquares(1);
14652
14653     if (gameMode == PlayFromGameFile && !pausing)
14654       PauseEvent();
14655
14656     if (gameMode == IcsExamining && pausing)
14657       limit = pauseExamForwardMostMove;
14658     else
14659       limit = forwardMostMove;
14660
14661     if (target > limit) target = limit;
14662
14663     if (target > 0 && moveList[target - 1][0]) {
14664         int fromX, fromY, toX, toY;
14665         toX = moveList[target - 1][2] - AAA;
14666         toY = moveList[target - 1][3] - ONE;
14667         if (moveList[target - 1][1] == '@') {
14668             if (appData.highlightLastMove) {
14669                 SetHighlights(-1, -1, toX, toY);
14670             }
14671         } else {
14672             fromX = moveList[target - 1][0] - AAA;
14673             fromY = moveList[target - 1][1] - ONE;
14674             if (target == currentMove + 1) {
14675                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14676             }
14677             if (appData.highlightLastMove) {
14678                 SetHighlights(fromX, fromY, toX, toY);
14679             }
14680         }
14681     }
14682     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14683         gameMode == Training || gameMode == PlayFromGameFile ||
14684         gameMode == AnalyzeFile) {
14685         while (currentMove < target) {
14686             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14687             SendMoveToProgram(currentMove++, &first);
14688         }
14689     } else {
14690         currentMove = target;
14691     }
14692
14693     if (gameMode == EditGame || gameMode == EndOfGame) {
14694         whiteTimeRemaining = timeRemaining[0][currentMove];
14695         blackTimeRemaining = timeRemaining[1][currentMove];
14696     }
14697     DisplayBothClocks();
14698     DisplayMove(currentMove - 1);
14699     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14700     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14701     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14702         DisplayComment(currentMove - 1, commentList[currentMove]);
14703     }
14704     ClearMap(); // [HGM] exclude: invalidate map
14705 }
14706
14707
14708 void
14709 ForwardEvent ()
14710 {
14711     if (gameMode == IcsExamining && !pausing) {
14712         SendToICS(ics_prefix);
14713         SendToICS("forward\n");
14714     } else {
14715         ForwardInner(currentMove + 1);
14716     }
14717 }
14718
14719 void
14720 ToEndEvent ()
14721 {
14722     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14723         /* to optimze, we temporarily turn off analysis mode while we feed
14724          * the remaining moves to the engine. Otherwise we get analysis output
14725          * after each move.
14726          */
14727         if (first.analysisSupport) {
14728           SendToProgram("exit\nforce\n", &first);
14729           first.analyzing = FALSE;
14730         }
14731     }
14732
14733     if (gameMode == IcsExamining && !pausing) {
14734         SendToICS(ics_prefix);
14735         SendToICS("forward 999999\n");
14736     } else {
14737         ForwardInner(forwardMostMove);
14738     }
14739
14740     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14741         /* we have fed all the moves, so reactivate analysis mode */
14742         SendToProgram("analyze\n", &first);
14743         first.analyzing = TRUE;
14744         /*first.maybeThinking = TRUE;*/
14745         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14746     }
14747 }
14748
14749 void
14750 BackwardInner (int target)
14751 {
14752     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14753
14754     if (appData.debugMode)
14755         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14756                 target, currentMove, forwardMostMove);
14757
14758     if (gameMode == EditPosition) return;
14759     seekGraphUp = FALSE;
14760     MarkTargetSquares(1);
14761     if (currentMove <= backwardMostMove) {
14762         ClearHighlights();
14763         DrawPosition(full_redraw, boards[currentMove]);
14764         return;
14765     }
14766     if (gameMode == PlayFromGameFile && !pausing)
14767       PauseEvent();
14768
14769     if (moveList[target][0]) {
14770         int fromX, fromY, toX, toY;
14771         toX = moveList[target][2] - AAA;
14772         toY = moveList[target][3] - ONE;
14773         if (moveList[target][1] == '@') {
14774             if (appData.highlightLastMove) {
14775                 SetHighlights(-1, -1, toX, toY);
14776             }
14777         } else {
14778             fromX = moveList[target][0] - AAA;
14779             fromY = moveList[target][1] - ONE;
14780             if (target == currentMove - 1) {
14781                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14782             }
14783             if (appData.highlightLastMove) {
14784                 SetHighlights(fromX, fromY, toX, toY);
14785             }
14786         }
14787     }
14788     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14789         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14790         while (currentMove > target) {
14791             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14792                 // null move cannot be undone. Reload program with move history before it.
14793                 int i;
14794                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14795                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14796                 }
14797                 SendBoard(&first, i); 
14798               if(second.analyzing) SendBoard(&second, i);
14799                 for(currentMove=i; currentMove<target; currentMove++) {
14800                     SendMoveToProgram(currentMove, &first);
14801                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14802                 }
14803                 break;
14804             }
14805             SendToBoth("undo\n");
14806             currentMove--;
14807         }
14808     } else {
14809         currentMove = target;
14810     }
14811
14812     if (gameMode == EditGame || gameMode == EndOfGame) {
14813         whiteTimeRemaining = timeRemaining[0][currentMove];
14814         blackTimeRemaining = timeRemaining[1][currentMove];
14815     }
14816     DisplayBothClocks();
14817     DisplayMove(currentMove - 1);
14818     DrawPosition(full_redraw, boards[currentMove]);
14819     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14820     // [HGM] PV info: routine tests if comment empty
14821     DisplayComment(currentMove - 1, commentList[currentMove]);
14822     ClearMap(); // [HGM] exclude: invalidate map
14823 }
14824
14825 void
14826 BackwardEvent ()
14827 {
14828     if (gameMode == IcsExamining && !pausing) {
14829         SendToICS(ics_prefix);
14830         SendToICS("backward\n");
14831     } else {
14832         BackwardInner(currentMove - 1);
14833     }
14834 }
14835
14836 void
14837 ToStartEvent ()
14838 {
14839     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14840         /* to optimize, we temporarily turn off analysis mode while we undo
14841          * all the moves. Otherwise we get analysis output after each undo.
14842          */
14843         if (first.analysisSupport) {
14844           SendToProgram("exit\nforce\n", &first);
14845           first.analyzing = FALSE;
14846         }
14847     }
14848
14849     if (gameMode == IcsExamining && !pausing) {
14850         SendToICS(ics_prefix);
14851         SendToICS("backward 999999\n");
14852     } else {
14853         BackwardInner(backwardMostMove);
14854     }
14855
14856     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14857         /* we have fed all the moves, so reactivate analysis mode */
14858         SendToProgram("analyze\n", &first);
14859         first.analyzing = TRUE;
14860         /*first.maybeThinking = TRUE;*/
14861         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14862     }
14863 }
14864
14865 void
14866 ToNrEvent (int to)
14867 {
14868   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14869   if (to >= forwardMostMove) to = forwardMostMove;
14870   if (to <= backwardMostMove) to = backwardMostMove;
14871   if (to < currentMove) {
14872     BackwardInner(to);
14873   } else {
14874     ForwardInner(to);
14875   }
14876 }
14877
14878 void
14879 RevertEvent (Boolean annotate)
14880 {
14881     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14882         return;
14883     }
14884     if (gameMode != IcsExamining) {
14885         DisplayError(_("You are not examining a game"), 0);
14886         return;
14887     }
14888     if (pausing) {
14889         DisplayError(_("You can't revert while pausing"), 0);
14890         return;
14891     }
14892     SendToICS(ics_prefix);
14893     SendToICS("revert\n");
14894 }
14895
14896 void
14897 RetractMoveEvent ()
14898 {
14899     switch (gameMode) {
14900       case MachinePlaysWhite:
14901       case MachinePlaysBlack:
14902         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14903             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14904             return;
14905         }
14906         if (forwardMostMove < 2) return;
14907         currentMove = forwardMostMove = forwardMostMove - 2;
14908         whiteTimeRemaining = timeRemaining[0][currentMove];
14909         blackTimeRemaining = timeRemaining[1][currentMove];
14910         DisplayBothClocks();
14911         DisplayMove(currentMove - 1);
14912         ClearHighlights();/*!! could figure this out*/
14913         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14914         SendToProgram("remove\n", &first);
14915         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14916         break;
14917
14918       case BeginningOfGame:
14919       default:
14920         break;
14921
14922       case IcsPlayingWhite:
14923       case IcsPlayingBlack:
14924         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14925             SendToICS(ics_prefix);
14926             SendToICS("takeback 2\n");
14927         } else {
14928             SendToICS(ics_prefix);
14929             SendToICS("takeback 1\n");
14930         }
14931         break;
14932     }
14933 }
14934
14935 void
14936 MoveNowEvent ()
14937 {
14938     ChessProgramState *cps;
14939
14940     switch (gameMode) {
14941       case MachinePlaysWhite:
14942         if (!WhiteOnMove(forwardMostMove)) {
14943             DisplayError(_("It is your turn"), 0);
14944             return;
14945         }
14946         cps = &first;
14947         break;
14948       case MachinePlaysBlack:
14949         if (WhiteOnMove(forwardMostMove)) {
14950             DisplayError(_("It is your turn"), 0);
14951             return;
14952         }
14953         cps = &first;
14954         break;
14955       case TwoMachinesPlay:
14956         if (WhiteOnMove(forwardMostMove) ==
14957             (first.twoMachinesColor[0] == 'w')) {
14958             cps = &first;
14959         } else {
14960             cps = &second;
14961         }
14962         break;
14963       case BeginningOfGame:
14964       default:
14965         return;
14966     }
14967     SendToProgram("?\n", cps);
14968 }
14969
14970 void
14971 TruncateGameEvent ()
14972 {
14973     EditGameEvent();
14974     if (gameMode != EditGame) return;
14975     TruncateGame();
14976 }
14977
14978 void
14979 TruncateGame ()
14980 {
14981     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14982     if (forwardMostMove > currentMove) {
14983         if (gameInfo.resultDetails != NULL) {
14984             free(gameInfo.resultDetails);
14985             gameInfo.resultDetails = NULL;
14986             gameInfo.result = GameUnfinished;
14987         }
14988         forwardMostMove = currentMove;
14989         HistorySet(parseList, backwardMostMove, forwardMostMove,
14990                    currentMove-1);
14991     }
14992 }
14993
14994 void
14995 HintEvent ()
14996 {
14997     if (appData.noChessProgram) return;
14998     switch (gameMode) {
14999       case MachinePlaysWhite:
15000         if (WhiteOnMove(forwardMostMove)) {
15001             DisplayError(_("Wait until your turn"), 0);
15002             return;
15003         }
15004         break;
15005       case BeginningOfGame:
15006       case MachinePlaysBlack:
15007         if (!WhiteOnMove(forwardMostMove)) {
15008             DisplayError(_("Wait until your turn"), 0);
15009             return;
15010         }
15011         break;
15012       default:
15013         DisplayError(_("No hint available"), 0);
15014         return;
15015     }
15016     SendToProgram("hint\n", &first);
15017     hintRequested = TRUE;
15018 }
15019
15020 void
15021 BookEvent ()
15022 {
15023     if (appData.noChessProgram) return;
15024     switch (gameMode) {
15025       case MachinePlaysWhite:
15026         if (WhiteOnMove(forwardMostMove)) {
15027             DisplayError(_("Wait until your turn"), 0);
15028             return;
15029         }
15030         break;
15031       case BeginningOfGame:
15032       case MachinePlaysBlack:
15033         if (!WhiteOnMove(forwardMostMove)) {
15034             DisplayError(_("Wait until your turn"), 0);
15035             return;
15036         }
15037         break;
15038       case EditPosition:
15039         EditPositionDone(TRUE);
15040         break;
15041       case TwoMachinesPlay:
15042         return;
15043       default:
15044         break;
15045     }
15046     SendToProgram("bk\n", &first);
15047     bookOutput[0] = NULLCHAR;
15048     bookRequested = TRUE;
15049 }
15050
15051 void
15052 AboutGameEvent ()
15053 {
15054     char *tags = PGNTags(&gameInfo);
15055     TagsPopUp(tags, CmailMsg());
15056     free(tags);
15057 }
15058
15059 /* end button procedures */
15060
15061 void
15062 PrintPosition (FILE *fp, int move)
15063 {
15064     int i, j;
15065
15066     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15067         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15068             char c = PieceToChar(boards[move][i][j]);
15069             fputc(c == 'x' ? '.' : c, fp);
15070             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15071         }
15072     }
15073     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15074       fprintf(fp, "white to play\n");
15075     else
15076       fprintf(fp, "black to play\n");
15077 }
15078
15079 void
15080 PrintOpponents (FILE *fp)
15081 {
15082     if (gameInfo.white != NULL) {
15083         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15084     } else {
15085         fprintf(fp, "\n");
15086     }
15087 }
15088
15089 /* Find last component of program's own name, using some heuristics */
15090 void
15091 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15092 {
15093     char *p, *q, c;
15094     int local = (strcmp(host, "localhost") == 0);
15095     while (!local && (p = strchr(prog, ';')) != NULL) {
15096         p++;
15097         while (*p == ' ') p++;
15098         prog = p;
15099     }
15100     if (*prog == '"' || *prog == '\'') {
15101         q = strchr(prog + 1, *prog);
15102     } else {
15103         q = strchr(prog, ' ');
15104     }
15105     if (q == NULL) q = prog + strlen(prog);
15106     p = q;
15107     while (p >= prog && *p != '/' && *p != '\\') p--;
15108     p++;
15109     if(p == prog && *p == '"') p++;
15110     c = *q; *q = 0;
15111     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15112     memcpy(buf, p, q - p);
15113     buf[q - p] = NULLCHAR;
15114     if (!local) {
15115         strcat(buf, "@");
15116         strcat(buf, host);
15117     }
15118 }
15119
15120 char *
15121 TimeControlTagValue ()
15122 {
15123     char buf[MSG_SIZ];
15124     if (!appData.clockMode) {
15125       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15126     } else if (movesPerSession > 0) {
15127       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15128     } else if (timeIncrement == 0) {
15129       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15130     } else {
15131       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15132     }
15133     return StrSave(buf);
15134 }
15135
15136 void
15137 SetGameInfo ()
15138 {
15139     /* This routine is used only for certain modes */
15140     VariantClass v = gameInfo.variant;
15141     ChessMove r = GameUnfinished;
15142     char *p = NULL;
15143
15144     if(keepInfo) return;
15145
15146     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15147         r = gameInfo.result;
15148         p = gameInfo.resultDetails;
15149         gameInfo.resultDetails = NULL;
15150     }
15151     ClearGameInfo(&gameInfo);
15152     gameInfo.variant = v;
15153
15154     switch (gameMode) {
15155       case MachinePlaysWhite:
15156         gameInfo.event = StrSave( appData.pgnEventHeader );
15157         gameInfo.site = StrSave(HostName());
15158         gameInfo.date = PGNDate();
15159         gameInfo.round = StrSave("-");
15160         gameInfo.white = StrSave(first.tidy);
15161         gameInfo.black = StrSave(UserName());
15162         gameInfo.timeControl = TimeControlTagValue();
15163         break;
15164
15165       case MachinePlaysBlack:
15166         gameInfo.event = StrSave( appData.pgnEventHeader );
15167         gameInfo.site = StrSave(HostName());
15168         gameInfo.date = PGNDate();
15169         gameInfo.round = StrSave("-");
15170         gameInfo.white = StrSave(UserName());
15171         gameInfo.black = StrSave(first.tidy);
15172         gameInfo.timeControl = TimeControlTagValue();
15173         break;
15174
15175       case TwoMachinesPlay:
15176         gameInfo.event = StrSave( appData.pgnEventHeader );
15177         gameInfo.site = StrSave(HostName());
15178         gameInfo.date = PGNDate();
15179         if (roundNr > 0) {
15180             char buf[MSG_SIZ];
15181             snprintf(buf, MSG_SIZ, "%d", roundNr);
15182             gameInfo.round = StrSave(buf);
15183         } else {
15184             gameInfo.round = StrSave("-");
15185         }
15186         if (first.twoMachinesColor[0] == 'w') {
15187             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15188             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15189         } else {
15190             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15191             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15192         }
15193         gameInfo.timeControl = TimeControlTagValue();
15194         break;
15195
15196       case EditGame:
15197         gameInfo.event = StrSave("Edited game");
15198         gameInfo.site = StrSave(HostName());
15199         gameInfo.date = PGNDate();
15200         gameInfo.round = StrSave("-");
15201         gameInfo.white = StrSave("-");
15202         gameInfo.black = StrSave("-");
15203         gameInfo.result = r;
15204         gameInfo.resultDetails = p;
15205         break;
15206
15207       case EditPosition:
15208         gameInfo.event = StrSave("Edited position");
15209         gameInfo.site = StrSave(HostName());
15210         gameInfo.date = PGNDate();
15211         gameInfo.round = StrSave("-");
15212         gameInfo.white = StrSave("-");
15213         gameInfo.black = StrSave("-");
15214         break;
15215
15216       case IcsPlayingWhite:
15217       case IcsPlayingBlack:
15218       case IcsObserving:
15219       case IcsExamining:
15220         break;
15221
15222       case PlayFromGameFile:
15223         gameInfo.event = StrSave("Game from non-PGN file");
15224         gameInfo.site = StrSave(HostName());
15225         gameInfo.date = PGNDate();
15226         gameInfo.round = StrSave("-");
15227         gameInfo.white = StrSave("?");
15228         gameInfo.black = StrSave("?");
15229         break;
15230
15231       default:
15232         break;
15233     }
15234 }
15235
15236 void
15237 ReplaceComment (int index, char *text)
15238 {
15239     int len;
15240     char *p;
15241     float score;
15242
15243     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15244        pvInfoList[index-1].depth == len &&
15245        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15246        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15247     while (*text == '\n') text++;
15248     len = strlen(text);
15249     while (len > 0 && text[len - 1] == '\n') len--;
15250
15251     if (commentList[index] != NULL)
15252       free(commentList[index]);
15253
15254     if (len == 0) {
15255         commentList[index] = NULL;
15256         return;
15257     }
15258   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15259       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15260       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15261     commentList[index] = (char *) malloc(len + 2);
15262     strncpy(commentList[index], text, len);
15263     commentList[index][len] = '\n';
15264     commentList[index][len + 1] = NULLCHAR;
15265   } else {
15266     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15267     char *p;
15268     commentList[index] = (char *) malloc(len + 7);
15269     safeStrCpy(commentList[index], "{\n", 3);
15270     safeStrCpy(commentList[index]+2, text, len+1);
15271     commentList[index][len+2] = NULLCHAR;
15272     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15273     strcat(commentList[index], "\n}\n");
15274   }
15275 }
15276
15277 void
15278 CrushCRs (char *text)
15279 {
15280   char *p = text;
15281   char *q = text;
15282   char ch;
15283
15284   do {
15285     ch = *p++;
15286     if (ch == '\r') continue;
15287     *q++ = ch;
15288   } while (ch != '\0');
15289 }
15290
15291 void
15292 AppendComment (int index, char *text, Boolean addBraces)
15293 /* addBraces  tells if we should add {} */
15294 {
15295     int oldlen, len;
15296     char *old;
15297
15298 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15299     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15300
15301     CrushCRs(text);
15302     while (*text == '\n') text++;
15303     len = strlen(text);
15304     while (len > 0 && text[len - 1] == '\n') len--;
15305     text[len] = NULLCHAR;
15306
15307     if (len == 0) return;
15308
15309     if (commentList[index] != NULL) {
15310       Boolean addClosingBrace = addBraces;
15311         old = commentList[index];
15312         oldlen = strlen(old);
15313         while(commentList[index][oldlen-1] ==  '\n')
15314           commentList[index][--oldlen] = NULLCHAR;
15315         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15316         safeStrCpy(commentList[index], old, oldlen + len + 6);
15317         free(old);
15318         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15319         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15320           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15321           while (*text == '\n') { text++; len--; }
15322           commentList[index][--oldlen] = NULLCHAR;
15323       }
15324         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15325         else          strcat(commentList[index], "\n");
15326         strcat(commentList[index], text);
15327         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15328         else          strcat(commentList[index], "\n");
15329     } else {
15330         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15331         if(addBraces)
15332           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15333         else commentList[index][0] = NULLCHAR;
15334         strcat(commentList[index], text);
15335         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15336         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15337     }
15338 }
15339
15340 static char *
15341 FindStr (char * text, char * sub_text)
15342 {
15343     char * result = strstr( text, sub_text );
15344
15345     if( result != NULL ) {
15346         result += strlen( sub_text );
15347     }
15348
15349     return result;
15350 }
15351
15352 /* [AS] Try to extract PV info from PGN comment */
15353 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15354 char *
15355 GetInfoFromComment (int index, char * text)
15356 {
15357     char * sep = text, *p;
15358
15359     if( text != NULL && index > 0 ) {
15360         int score = 0;
15361         int depth = 0;
15362         int time = -1, sec = 0, deci;
15363         char * s_eval = FindStr( text, "[%eval " );
15364         char * s_emt = FindStr( text, "[%emt " );
15365
15366         if( s_eval != NULL || s_emt != NULL ) {
15367             /* New style */
15368             char delim;
15369
15370             if( s_eval != NULL ) {
15371                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15372                     return text;
15373                 }
15374
15375                 if( delim != ']' ) {
15376                     return text;
15377                 }
15378             }
15379
15380             if( s_emt != NULL ) {
15381             }
15382                 return text;
15383         }
15384         else {
15385             /* We expect something like: [+|-]nnn.nn/dd */
15386             int score_lo = 0;
15387
15388             if(*text != '{') return text; // [HGM] braces: must be normal comment
15389
15390             sep = strchr( text, '/' );
15391             if( sep == NULL || sep < (text+4) ) {
15392                 return text;
15393             }
15394
15395             p = text;
15396             if(p[1] == '(') { // comment starts with PV
15397                p = strchr(p, ')'); // locate end of PV
15398                if(p == NULL || sep < p+5) return text;
15399                // at this point we have something like "{(.*) +0.23/6 ..."
15400                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15401                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15402                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15403             }
15404             time = -1; sec = -1; deci = -1;
15405             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15406                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15407                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15408                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15409                 return text;
15410             }
15411
15412             if( score_lo < 0 || score_lo >= 100 ) {
15413                 return text;
15414             }
15415
15416             if(sec >= 0) time = 600*time + 10*sec; else
15417             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15418
15419             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15420
15421             /* [HGM] PV time: now locate end of PV info */
15422             while( *++sep >= '0' && *sep <= '9'); // strip depth
15423             if(time >= 0)
15424             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15425             if(sec >= 0)
15426             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15427             if(deci >= 0)
15428             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15429             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15430         }
15431
15432         if( depth <= 0 ) {
15433             return text;
15434         }
15435
15436         if( time < 0 ) {
15437             time = -1;
15438         }
15439
15440         pvInfoList[index-1].depth = depth;
15441         pvInfoList[index-1].score = score;
15442         pvInfoList[index-1].time  = 10*time; // centi-sec
15443         if(*sep == '}') *sep = 0; else *--sep = '{';
15444         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15445     }
15446     return sep;
15447 }
15448
15449 void
15450 SendToProgram (char *message, ChessProgramState *cps)
15451 {
15452     int count, outCount, error;
15453     char buf[MSG_SIZ];
15454
15455     if (cps->pr == NoProc) return;
15456     Attention(cps);
15457
15458     if (appData.debugMode) {
15459         TimeMark now;
15460         GetTimeMark(&now);
15461         fprintf(debugFP, "%ld >%-6s: %s",
15462                 SubtractTimeMarks(&now, &programStartTime),
15463                 cps->which, message);
15464         if(serverFP)
15465             fprintf(serverFP, "%ld >%-6s: %s",
15466                 SubtractTimeMarks(&now, &programStartTime),
15467                 cps->which, message), fflush(serverFP);
15468     }
15469
15470     count = strlen(message);
15471     outCount = OutputToProcess(cps->pr, message, count, &error);
15472     if (outCount < count && !exiting
15473                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15474       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15475       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15476         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15477             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15478                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15479                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15480                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15481             } else {
15482                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15483                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15484                 gameInfo.result = res;
15485             }
15486             gameInfo.resultDetails = StrSave(buf);
15487         }
15488         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15489         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15490     }
15491 }
15492
15493 void
15494 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15495 {
15496     char *end_str;
15497     char buf[MSG_SIZ];
15498     ChessProgramState *cps = (ChessProgramState *)closure;
15499
15500     if (isr != cps->isr) return; /* Killed intentionally */
15501     if (count <= 0) {
15502         if (count == 0) {
15503             RemoveInputSource(cps->isr);
15504             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15505                     _(cps->which), cps->program);
15506             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15507             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15508                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15509                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15510                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15511                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15512                 } else {
15513                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15514                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15515                     gameInfo.result = res;
15516                 }
15517                 gameInfo.resultDetails = StrSave(buf);
15518             }
15519             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15520             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15521         } else {
15522             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15523                     _(cps->which), cps->program);
15524             RemoveInputSource(cps->isr);
15525
15526             /* [AS] Program is misbehaving badly... kill it */
15527             if( count == -2 ) {
15528                 DestroyChildProcess( cps->pr, 9 );
15529                 cps->pr = NoProc;
15530             }
15531
15532             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15533         }
15534         return;
15535     }
15536
15537     if ((end_str = strchr(message, '\r')) != NULL)
15538       *end_str = NULLCHAR;
15539     if ((end_str = strchr(message, '\n')) != NULL)
15540       *end_str = NULLCHAR;
15541
15542     if (appData.debugMode) {
15543         TimeMark now; int print = 1;
15544         char *quote = ""; char c; int i;
15545
15546         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15547                 char start = message[0];
15548                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15549                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15550                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15551                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15552                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15553                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15554                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15555                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15556                    sscanf(message, "hint: %c", &c)!=1 && 
15557                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15558                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15559                     print = (appData.engineComments >= 2);
15560                 }
15561                 message[0] = start; // restore original message
15562         }
15563         if(print) {
15564                 GetTimeMark(&now);
15565                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15566                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15567                         quote,
15568                         message);
15569                 if(serverFP)
15570                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15571                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15572                         quote,
15573                         message), fflush(serverFP);
15574         }
15575     }
15576
15577     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15578     if (appData.icsEngineAnalyze) {
15579         if (strstr(message, "whisper") != NULL ||
15580              strstr(message, "kibitz") != NULL ||
15581             strstr(message, "tellics") != NULL) return;
15582     }
15583
15584     HandleMachineMove(message, cps);
15585 }
15586
15587
15588 void
15589 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15590 {
15591     char buf[MSG_SIZ];
15592     int seconds;
15593
15594     if( timeControl_2 > 0 ) {
15595         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15596             tc = timeControl_2;
15597         }
15598     }
15599     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15600     inc /= cps->timeOdds;
15601     st  /= cps->timeOdds;
15602
15603     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15604
15605     if (st > 0) {
15606       /* Set exact time per move, normally using st command */
15607       if (cps->stKludge) {
15608         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15609         seconds = st % 60;
15610         if (seconds == 0) {
15611           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15612         } else {
15613           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15614         }
15615       } else {
15616         snprintf(buf, MSG_SIZ, "st %d\n", st);
15617       }
15618     } else {
15619       /* Set conventional or incremental time control, using level command */
15620       if (seconds == 0) {
15621         /* Note old gnuchess bug -- minutes:seconds used to not work.
15622            Fixed in later versions, but still avoid :seconds
15623            when seconds is 0. */
15624         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15625       } else {
15626         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15627                  seconds, inc/1000.);
15628       }
15629     }
15630     SendToProgram(buf, cps);
15631
15632     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15633     /* Orthogonally, limit search to given depth */
15634     if (sd > 0) {
15635       if (cps->sdKludge) {
15636         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15637       } else {
15638         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15639       }
15640       SendToProgram(buf, cps);
15641     }
15642
15643     if(cps->nps >= 0) { /* [HGM] nps */
15644         if(cps->supportsNPS == FALSE)
15645           cps->nps = -1; // don't use if engine explicitly says not supported!
15646         else {
15647           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15648           SendToProgram(buf, cps);
15649         }
15650     }
15651 }
15652
15653 ChessProgramState *
15654 WhitePlayer ()
15655 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15656 {
15657     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15658        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15659         return &second;
15660     return &first;
15661 }
15662
15663 void
15664 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15665 {
15666     char message[MSG_SIZ];
15667     long time, otime;
15668
15669     /* Note: this routine must be called when the clocks are stopped
15670        or when they have *just* been set or switched; otherwise
15671        it will be off by the time since the current tick started.
15672     */
15673     if (machineWhite) {
15674         time = whiteTimeRemaining / 10;
15675         otime = blackTimeRemaining / 10;
15676     } else {
15677         time = blackTimeRemaining / 10;
15678         otime = whiteTimeRemaining / 10;
15679     }
15680     /* [HGM] translate opponent's time by time-odds factor */
15681     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15682
15683     if (time <= 0) time = 1;
15684     if (otime <= 0) otime = 1;
15685
15686     snprintf(message, MSG_SIZ, "time %ld\n", time);
15687     SendToProgram(message, cps);
15688
15689     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15690     SendToProgram(message, cps);
15691 }
15692
15693 int
15694 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15695 {
15696   char buf[MSG_SIZ];
15697   int len = strlen(name);
15698   int val;
15699
15700   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15701     (*p) += len + 1;
15702     sscanf(*p, "%d", &val);
15703     *loc = (val != 0);
15704     while (**p && **p != ' ')
15705       (*p)++;
15706     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15707     SendToProgram(buf, cps);
15708     return TRUE;
15709   }
15710   return FALSE;
15711 }
15712
15713 int
15714 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15715 {
15716   char buf[MSG_SIZ];
15717   int len = strlen(name);
15718   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15719     (*p) += len + 1;
15720     sscanf(*p, "%d", loc);
15721     while (**p && **p != ' ') (*p)++;
15722     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15723     SendToProgram(buf, cps);
15724     return TRUE;
15725   }
15726   return FALSE;
15727 }
15728
15729 int
15730 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15731 {
15732   char buf[MSG_SIZ];
15733   int len = strlen(name);
15734   if (strncmp((*p), name, len) == 0
15735       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15736     (*p) += len + 2;
15737     sscanf(*p, "%[^\"]", loc);
15738     while (**p && **p != '\"') (*p)++;
15739     if (**p == '\"') (*p)++;
15740     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15741     SendToProgram(buf, cps);
15742     return TRUE;
15743   }
15744   return FALSE;
15745 }
15746
15747 int
15748 ParseOption (Option *opt, ChessProgramState *cps)
15749 // [HGM] options: process the string that defines an engine option, and determine
15750 // name, type, default value, and allowed value range
15751 {
15752         char *p, *q, buf[MSG_SIZ];
15753         int n, min = (-1)<<31, max = 1<<31, def;
15754
15755         if(p = strstr(opt->name, " -spin ")) {
15756             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15757             if(max < min) max = min; // enforce consistency
15758             if(def < min) def = min;
15759             if(def > max) def = max;
15760             opt->value = def;
15761             opt->min = min;
15762             opt->max = max;
15763             opt->type = Spin;
15764         } else if((p = strstr(opt->name, " -slider "))) {
15765             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15766             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15767             if(max < min) max = min; // enforce consistency
15768             if(def < min) def = min;
15769             if(def > max) def = max;
15770             opt->value = def;
15771             opt->min = min;
15772             opt->max = max;
15773             opt->type = Spin; // Slider;
15774         } else if((p = strstr(opt->name, " -string "))) {
15775             opt->textValue = p+9;
15776             opt->type = TextBox;
15777         } else if((p = strstr(opt->name, " -file "))) {
15778             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15779             opt->textValue = p+7;
15780             opt->type = FileName; // FileName;
15781         } else if((p = strstr(opt->name, " -path "))) {
15782             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15783             opt->textValue = p+7;
15784             opt->type = PathName; // PathName;
15785         } else if(p = strstr(opt->name, " -check ")) {
15786             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15787             opt->value = (def != 0);
15788             opt->type = CheckBox;
15789         } else if(p = strstr(opt->name, " -combo ")) {
15790             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15791             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15792             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15793             opt->value = n = 0;
15794             while(q = StrStr(q, " /// ")) {
15795                 n++; *q = 0;    // count choices, and null-terminate each of them
15796                 q += 5;
15797                 if(*q == '*') { // remember default, which is marked with * prefix
15798                     q++;
15799                     opt->value = n;
15800                 }
15801                 cps->comboList[cps->comboCnt++] = q;
15802             }
15803             cps->comboList[cps->comboCnt++] = NULL;
15804             opt->max = n + 1;
15805             opt->type = ComboBox;
15806         } else if(p = strstr(opt->name, " -button")) {
15807             opt->type = Button;
15808         } else if(p = strstr(opt->name, " -save")) {
15809             opt->type = SaveButton;
15810         } else return FALSE;
15811         *p = 0; // terminate option name
15812         // now look if the command-line options define a setting for this engine option.
15813         if(cps->optionSettings && cps->optionSettings[0])
15814             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15815         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15816           snprintf(buf, MSG_SIZ, "option %s", p);
15817                 if(p = strstr(buf, ",")) *p = 0;
15818                 if(q = strchr(buf, '=')) switch(opt->type) {
15819                     case ComboBox:
15820                         for(n=0; n<opt->max; n++)
15821                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15822                         break;
15823                     case TextBox:
15824                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15825                         break;
15826                     case Spin:
15827                     case CheckBox:
15828                         opt->value = atoi(q+1);
15829                     default:
15830                         break;
15831                 }
15832                 strcat(buf, "\n");
15833                 SendToProgram(buf, cps);
15834         }
15835         return TRUE;
15836 }
15837
15838 void
15839 FeatureDone (ChessProgramState *cps, int val)
15840 {
15841   DelayedEventCallback cb = GetDelayedEvent();
15842   if ((cb == InitBackEnd3 && cps == &first) ||
15843       (cb == SettingsMenuIfReady && cps == &second) ||
15844       (cb == LoadEngine) ||
15845       (cb == TwoMachinesEventIfReady)) {
15846     CancelDelayedEvent();
15847     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15848   }
15849   cps->initDone = val;
15850 }
15851
15852 /* Parse feature command from engine */
15853 void
15854 ParseFeatures (char *args, ChessProgramState *cps)
15855 {
15856   char *p = args;
15857   char *q;
15858   int val;
15859   char buf[MSG_SIZ];
15860
15861   for (;;) {
15862     while (*p == ' ') p++;
15863     if (*p == NULLCHAR) return;
15864
15865     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15866     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15867     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15868     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15869     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15870     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15871     if (BoolFeature(&p, "reuse", &val, cps)) {
15872       /* Engine can disable reuse, but can't enable it if user said no */
15873       if (!val) cps->reuse = FALSE;
15874       continue;
15875     }
15876     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15877     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15878       if (gameMode == TwoMachinesPlay) {
15879         DisplayTwoMachinesTitle();
15880       } else {
15881         DisplayTitle("");
15882       }
15883       continue;
15884     }
15885     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15886     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15887     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15888     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15889     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15890     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15891     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15892     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15893     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15894     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15895     if (IntFeature(&p, "done", &val, cps)) {
15896       FeatureDone(cps, val);
15897       continue;
15898     }
15899     /* Added by Tord: */
15900     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15901     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15902     /* End of additions by Tord */
15903
15904     /* [HGM] added features: */
15905     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15906     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15907     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15908     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15909     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15910     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15911     if (StringFeature(&p, "option", buf, cps)) {
15912         FREE(cps->option[cps->nrOptions].name);
15913         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15914         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15915         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15916           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15917             SendToProgram(buf, cps);
15918             continue;
15919         }
15920         if(cps->nrOptions >= MAX_OPTIONS) {
15921             cps->nrOptions--;
15922             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15923             DisplayError(buf, 0);
15924         }
15925         continue;
15926     }
15927     /* End of additions by HGM */
15928
15929     /* unknown feature: complain and skip */
15930     q = p;
15931     while (*q && *q != '=') q++;
15932     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15933     SendToProgram(buf, cps);
15934     p = q;
15935     if (*p == '=') {
15936       p++;
15937       if (*p == '\"') {
15938         p++;
15939         while (*p && *p != '\"') p++;
15940         if (*p == '\"') p++;
15941       } else {
15942         while (*p && *p != ' ') p++;
15943       }
15944     }
15945   }
15946
15947 }
15948
15949 void
15950 PeriodicUpdatesEvent (int newState)
15951 {
15952     if (newState == appData.periodicUpdates)
15953       return;
15954
15955     appData.periodicUpdates=newState;
15956
15957     /* Display type changes, so update it now */
15958 //    DisplayAnalysis();
15959
15960     /* Get the ball rolling again... */
15961     if (newState) {
15962         AnalysisPeriodicEvent(1);
15963         StartAnalysisClock();
15964     }
15965 }
15966
15967 void
15968 PonderNextMoveEvent (int newState)
15969 {
15970     if (newState == appData.ponderNextMove) return;
15971     if (gameMode == EditPosition) EditPositionDone(TRUE);
15972     if (newState) {
15973         SendToProgram("hard\n", &first);
15974         if (gameMode == TwoMachinesPlay) {
15975             SendToProgram("hard\n", &second);
15976         }
15977     } else {
15978         SendToProgram("easy\n", &first);
15979         thinkOutput[0] = NULLCHAR;
15980         if (gameMode == TwoMachinesPlay) {
15981             SendToProgram("easy\n", &second);
15982         }
15983     }
15984     appData.ponderNextMove = newState;
15985 }
15986
15987 void
15988 NewSettingEvent (int option, int *feature, char *command, int value)
15989 {
15990     char buf[MSG_SIZ];
15991
15992     if (gameMode == EditPosition) EditPositionDone(TRUE);
15993     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15994     if(feature == NULL || *feature) SendToProgram(buf, &first);
15995     if (gameMode == TwoMachinesPlay) {
15996         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15997     }
15998 }
15999
16000 void
16001 ShowThinkingEvent ()
16002 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16003 {
16004     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16005     int newState = appData.showThinking
16006         // [HGM] thinking: other features now need thinking output as well
16007         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16008
16009     if (oldState == newState) return;
16010     oldState = newState;
16011     if (gameMode == EditPosition) EditPositionDone(TRUE);
16012     if (oldState) {
16013         SendToProgram("post\n", &first);
16014         if (gameMode == TwoMachinesPlay) {
16015             SendToProgram("post\n", &second);
16016         }
16017     } else {
16018         SendToProgram("nopost\n", &first);
16019         thinkOutput[0] = NULLCHAR;
16020         if (gameMode == TwoMachinesPlay) {
16021             SendToProgram("nopost\n", &second);
16022         }
16023     }
16024 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16025 }
16026
16027 void
16028 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16029 {
16030   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16031   if (pr == NoProc) return;
16032   AskQuestion(title, question, replyPrefix, pr);
16033 }
16034
16035 void
16036 TypeInEvent (char firstChar)
16037 {
16038     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
16039         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16040         gameMode == AnalyzeMode || gameMode == EditGame || 
16041         gameMode == EditPosition || gameMode == IcsExamining ||
16042         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16043         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16044                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16045                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16046         gameMode == Training) PopUpMoveDialog(firstChar);
16047 }
16048
16049 void
16050 TypeInDoneEvent (char *move)
16051 {
16052         Board board;
16053         int n, fromX, fromY, toX, toY;
16054         char promoChar;
16055         ChessMove moveType;
16056
16057         // [HGM] FENedit
16058         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16059                 EditPositionPasteFEN(move);
16060                 return;
16061         }
16062         // [HGM] movenum: allow move number to be typed in any mode
16063         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16064           ToNrEvent(2*n-1);
16065           return;
16066         }
16067         // undocumented kludge: allow command-line option to be typed in!
16068         // (potentially fatal, and does not implement the effect of the option.)
16069         // should only be used for options that are values on which future decisions will be made,
16070         // and definitely not on options that would be used during initialization.
16071         if(strstr(move, "!!! -") == move) {
16072             ParseArgsFromString(move+4);
16073             return;
16074         }
16075
16076       if (gameMode != EditGame && currentMove != forwardMostMove && 
16077         gameMode != Training) {
16078         DisplayMoveError(_("Displayed move is not current"));
16079       } else {
16080         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16081           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16082         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16083         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16084           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16085           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16086         } else {
16087           DisplayMoveError(_("Could not parse move"));
16088         }
16089       }
16090 }
16091
16092 void
16093 DisplayMove (int moveNumber)
16094 {
16095     char message[MSG_SIZ];
16096     char res[MSG_SIZ];
16097     char cpThinkOutput[MSG_SIZ];
16098
16099     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16100
16101     if (moveNumber == forwardMostMove - 1 ||
16102         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16103
16104         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16105
16106         if (strchr(cpThinkOutput, '\n')) {
16107             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16108         }
16109     } else {
16110         *cpThinkOutput = NULLCHAR;
16111     }
16112
16113     /* [AS] Hide thinking from human user */
16114     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16115         *cpThinkOutput = NULLCHAR;
16116         if( thinkOutput[0] != NULLCHAR ) {
16117             int i;
16118
16119             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16120                 cpThinkOutput[i] = '.';
16121             }
16122             cpThinkOutput[i] = NULLCHAR;
16123             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16124         }
16125     }
16126
16127     if (moveNumber == forwardMostMove - 1 &&
16128         gameInfo.resultDetails != NULL) {
16129         if (gameInfo.resultDetails[0] == NULLCHAR) {
16130           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16131         } else {
16132           snprintf(res, MSG_SIZ, " {%s} %s",
16133                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16134         }
16135     } else {
16136         res[0] = NULLCHAR;
16137     }
16138
16139     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16140         DisplayMessage(res, cpThinkOutput);
16141     } else {
16142       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16143                 WhiteOnMove(moveNumber) ? " " : ".. ",
16144                 parseList[moveNumber], res);
16145         DisplayMessage(message, cpThinkOutput);
16146     }
16147 }
16148
16149 void
16150 DisplayComment (int moveNumber, char *text)
16151 {
16152     char title[MSG_SIZ];
16153
16154     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16155       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16156     } else {
16157       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16158               WhiteOnMove(moveNumber) ? " " : ".. ",
16159               parseList[moveNumber]);
16160     }
16161     if (text != NULL && (appData.autoDisplayComment || commentUp))
16162         CommentPopUp(title, text);
16163 }
16164
16165 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16166  * might be busy thinking or pondering.  It can be omitted if your
16167  * gnuchess is configured to stop thinking immediately on any user
16168  * input.  However, that gnuchess feature depends on the FIONREAD
16169  * ioctl, which does not work properly on some flavors of Unix.
16170  */
16171 void
16172 Attention (ChessProgramState *cps)
16173 {
16174 #if ATTENTION
16175     if (!cps->useSigint) return;
16176     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16177     switch (gameMode) {
16178       case MachinePlaysWhite:
16179       case MachinePlaysBlack:
16180       case TwoMachinesPlay:
16181       case IcsPlayingWhite:
16182       case IcsPlayingBlack:
16183       case AnalyzeMode:
16184       case AnalyzeFile:
16185         /* Skip if we know it isn't thinking */
16186         if (!cps->maybeThinking) return;
16187         if (appData.debugMode)
16188           fprintf(debugFP, "Interrupting %s\n", cps->which);
16189         InterruptChildProcess(cps->pr);
16190         cps->maybeThinking = FALSE;
16191         break;
16192       default:
16193         break;
16194     }
16195 #endif /*ATTENTION*/
16196 }
16197
16198 int
16199 CheckFlags ()
16200 {
16201     if (whiteTimeRemaining <= 0) {
16202         if (!whiteFlag) {
16203             whiteFlag = TRUE;
16204             if (appData.icsActive) {
16205                 if (appData.autoCallFlag &&
16206                     gameMode == IcsPlayingBlack && !blackFlag) {
16207                   SendToICS(ics_prefix);
16208                   SendToICS("flag\n");
16209                 }
16210             } else {
16211                 if (blackFlag) {
16212                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16213                 } else {
16214                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16215                     if (appData.autoCallFlag) {
16216                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16217                         return TRUE;
16218                     }
16219                 }
16220             }
16221         }
16222     }
16223     if (blackTimeRemaining <= 0) {
16224         if (!blackFlag) {
16225             blackFlag = TRUE;
16226             if (appData.icsActive) {
16227                 if (appData.autoCallFlag &&
16228                     gameMode == IcsPlayingWhite && !whiteFlag) {
16229                   SendToICS(ics_prefix);
16230                   SendToICS("flag\n");
16231                 }
16232             } else {
16233                 if (whiteFlag) {
16234                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16235                 } else {
16236                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16237                     if (appData.autoCallFlag) {
16238                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16239                         return TRUE;
16240                     }
16241                 }
16242             }
16243         }
16244     }
16245     return FALSE;
16246 }
16247
16248 void
16249 CheckTimeControl ()
16250 {
16251     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16252         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16253
16254     /*
16255      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16256      */
16257     if ( !WhiteOnMove(forwardMostMove) ) {
16258         /* White made time control */
16259         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16260         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16261         /* [HGM] time odds: correct new time quota for time odds! */
16262                                             / WhitePlayer()->timeOdds;
16263         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16264     } else {
16265         lastBlack -= blackTimeRemaining;
16266         /* Black made time control */
16267         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16268                                             / WhitePlayer()->other->timeOdds;
16269         lastWhite = whiteTimeRemaining;
16270     }
16271 }
16272
16273 void
16274 DisplayBothClocks ()
16275 {
16276     int wom = gameMode == EditPosition ?
16277       !blackPlaysFirst : WhiteOnMove(currentMove);
16278     DisplayWhiteClock(whiteTimeRemaining, wom);
16279     DisplayBlackClock(blackTimeRemaining, !wom);
16280 }
16281
16282
16283 /* Timekeeping seems to be a portability nightmare.  I think everyone
16284    has ftime(), but I'm really not sure, so I'm including some ifdefs
16285    to use other calls if you don't.  Clocks will be less accurate if
16286    you have neither ftime nor gettimeofday.
16287 */
16288
16289 /* VS 2008 requires the #include outside of the function */
16290 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16291 #include <sys/timeb.h>
16292 #endif
16293
16294 /* Get the current time as a TimeMark */
16295 void
16296 GetTimeMark (TimeMark *tm)
16297 {
16298 #if HAVE_GETTIMEOFDAY
16299
16300     struct timeval timeVal;
16301     struct timezone timeZone;
16302
16303     gettimeofday(&timeVal, &timeZone);
16304     tm->sec = (long) timeVal.tv_sec;
16305     tm->ms = (int) (timeVal.tv_usec / 1000L);
16306
16307 #else /*!HAVE_GETTIMEOFDAY*/
16308 #if HAVE_FTIME
16309
16310 // include <sys/timeb.h> / moved to just above start of function
16311     struct timeb timeB;
16312
16313     ftime(&timeB);
16314     tm->sec = (long) timeB.time;
16315     tm->ms = (int) timeB.millitm;
16316
16317 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16318     tm->sec = (long) time(NULL);
16319     tm->ms = 0;
16320 #endif
16321 #endif
16322 }
16323
16324 /* Return the difference in milliseconds between two
16325    time marks.  We assume the difference will fit in a long!
16326 */
16327 long
16328 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16329 {
16330     return 1000L*(tm2->sec - tm1->sec) +
16331            (long) (tm2->ms - tm1->ms);
16332 }
16333
16334
16335 /*
16336  * Code to manage the game clocks.
16337  *
16338  * In tournament play, black starts the clock and then white makes a move.
16339  * We give the human user a slight advantage if he is playing white---the
16340  * clocks don't run until he makes his first move, so it takes zero time.
16341  * Also, we don't account for network lag, so we could get out of sync
16342  * with GNU Chess's clock -- but then, referees are always right.
16343  */
16344
16345 static TimeMark tickStartTM;
16346 static long intendedTickLength;
16347
16348 long
16349 NextTickLength (long timeRemaining)
16350 {
16351     long nominalTickLength, nextTickLength;
16352
16353     if (timeRemaining > 0L && timeRemaining <= 10000L)
16354       nominalTickLength = 100L;
16355     else
16356       nominalTickLength = 1000L;
16357     nextTickLength = timeRemaining % nominalTickLength;
16358     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16359
16360     return nextTickLength;
16361 }
16362
16363 /* Adjust clock one minute up or down */
16364 void
16365 AdjustClock (Boolean which, int dir)
16366 {
16367     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16368     if(which) blackTimeRemaining += 60000*dir;
16369     else      whiteTimeRemaining += 60000*dir;
16370     DisplayBothClocks();
16371     adjustedClock = TRUE;
16372 }
16373
16374 /* Stop clocks and reset to a fresh time control */
16375 void
16376 ResetClocks ()
16377 {
16378     (void) StopClockTimer();
16379     if (appData.icsActive) {
16380         whiteTimeRemaining = blackTimeRemaining = 0;
16381     } else if (searchTime) {
16382         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16383         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16384     } else { /* [HGM] correct new time quote for time odds */
16385         whiteTC = blackTC = fullTimeControlString;
16386         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16387         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16388     }
16389     if (whiteFlag || blackFlag) {
16390         DisplayTitle("");
16391         whiteFlag = blackFlag = FALSE;
16392     }
16393     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16394     DisplayBothClocks();
16395     adjustedClock = FALSE;
16396 }
16397
16398 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16399
16400 /* Decrement running clock by amount of time that has passed */
16401 void
16402 DecrementClocks ()
16403 {
16404     long timeRemaining;
16405     long lastTickLength, fudge;
16406     TimeMark now;
16407
16408     if (!appData.clockMode) return;
16409     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16410
16411     GetTimeMark(&now);
16412
16413     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16414
16415     /* Fudge if we woke up a little too soon */
16416     fudge = intendedTickLength - lastTickLength;
16417     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16418
16419     if (WhiteOnMove(forwardMostMove)) {
16420         if(whiteNPS >= 0) lastTickLength = 0;
16421         timeRemaining = whiteTimeRemaining -= lastTickLength;
16422         if(timeRemaining < 0 && !appData.icsActive) {
16423             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16424             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16425                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16426                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16427             }
16428         }
16429         DisplayWhiteClock(whiteTimeRemaining - fudge,
16430                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16431     } else {
16432         if(blackNPS >= 0) lastTickLength = 0;
16433         timeRemaining = blackTimeRemaining -= lastTickLength;
16434         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16435             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16436             if(suddenDeath) {
16437                 blackStartMove = forwardMostMove;
16438                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16439             }
16440         }
16441         DisplayBlackClock(blackTimeRemaining - fudge,
16442                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16443     }
16444     if (CheckFlags()) return;
16445
16446     if(twoBoards) { // count down secondary board's clocks as well
16447         activePartnerTime -= lastTickLength;
16448         partnerUp = 1;
16449         if(activePartner == 'W')
16450             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16451         else
16452             DisplayBlackClock(activePartnerTime, TRUE);
16453         partnerUp = 0;
16454     }
16455
16456     tickStartTM = now;
16457     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16458     StartClockTimer(intendedTickLength);
16459
16460     /* if the time remaining has fallen below the alarm threshold, sound the
16461      * alarm. if the alarm has sounded and (due to a takeback or time control
16462      * with increment) the time remaining has increased to a level above the
16463      * threshold, reset the alarm so it can sound again.
16464      */
16465
16466     if (appData.icsActive && appData.icsAlarm) {
16467
16468         /* make sure we are dealing with the user's clock */
16469         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16470                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16471            )) return;
16472
16473         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16474             alarmSounded = FALSE;
16475         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16476             PlayAlarmSound();
16477             alarmSounded = TRUE;
16478         }
16479     }
16480 }
16481
16482
16483 /* A player has just moved, so stop the previously running
16484    clock and (if in clock mode) start the other one.
16485    We redisplay both clocks in case we're in ICS mode, because
16486    ICS gives us an update to both clocks after every move.
16487    Note that this routine is called *after* forwardMostMove
16488    is updated, so the last fractional tick must be subtracted
16489    from the color that is *not* on move now.
16490 */
16491 void
16492 SwitchClocks (int newMoveNr)
16493 {
16494     long lastTickLength;
16495     TimeMark now;
16496     int flagged = FALSE;
16497
16498     GetTimeMark(&now);
16499
16500     if (StopClockTimer() && appData.clockMode) {
16501         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16502         if (!WhiteOnMove(forwardMostMove)) {
16503             if(blackNPS >= 0) lastTickLength = 0;
16504             blackTimeRemaining -= lastTickLength;
16505            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16506 //         if(pvInfoList[forwardMostMove].time == -1)
16507                  pvInfoList[forwardMostMove].time =               // use GUI time
16508                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16509         } else {
16510            if(whiteNPS >= 0) lastTickLength = 0;
16511            whiteTimeRemaining -= lastTickLength;
16512            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16513 //         if(pvInfoList[forwardMostMove].time == -1)
16514                  pvInfoList[forwardMostMove].time =
16515                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16516         }
16517         flagged = CheckFlags();
16518     }
16519     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16520     CheckTimeControl();
16521
16522     if (flagged || !appData.clockMode) return;
16523
16524     switch (gameMode) {
16525       case MachinePlaysBlack:
16526       case MachinePlaysWhite:
16527       case BeginningOfGame:
16528         if (pausing) return;
16529         break;
16530
16531       case EditGame:
16532       case PlayFromGameFile:
16533       case IcsExamining:
16534         return;
16535
16536       default:
16537         break;
16538     }
16539
16540     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16541         if(WhiteOnMove(forwardMostMove))
16542              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16543         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16544     }
16545
16546     tickStartTM = now;
16547     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16548       whiteTimeRemaining : blackTimeRemaining);
16549     StartClockTimer(intendedTickLength);
16550 }
16551
16552
16553 /* Stop both clocks */
16554 void
16555 StopClocks ()
16556 {
16557     long lastTickLength;
16558     TimeMark now;
16559
16560     if (!StopClockTimer()) return;
16561     if (!appData.clockMode) return;
16562
16563     GetTimeMark(&now);
16564
16565     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16566     if (WhiteOnMove(forwardMostMove)) {
16567         if(whiteNPS >= 0) lastTickLength = 0;
16568         whiteTimeRemaining -= lastTickLength;
16569         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16570     } else {
16571         if(blackNPS >= 0) lastTickLength = 0;
16572         blackTimeRemaining -= lastTickLength;
16573         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16574     }
16575     CheckFlags();
16576 }
16577
16578 /* Start clock of player on move.  Time may have been reset, so
16579    if clock is already running, stop and restart it. */
16580 void
16581 StartClocks ()
16582 {
16583     (void) StopClockTimer(); /* in case it was running already */
16584     DisplayBothClocks();
16585     if (CheckFlags()) return;
16586
16587     if (!appData.clockMode) return;
16588     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16589
16590     GetTimeMark(&tickStartTM);
16591     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16592       whiteTimeRemaining : blackTimeRemaining);
16593
16594    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16595     whiteNPS = blackNPS = -1;
16596     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16597        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16598         whiteNPS = first.nps;
16599     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16600        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16601         blackNPS = first.nps;
16602     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16603         whiteNPS = second.nps;
16604     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16605         blackNPS = second.nps;
16606     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16607
16608     StartClockTimer(intendedTickLength);
16609 }
16610
16611 char *
16612 TimeString (long ms)
16613 {
16614     long second, minute, hour, day;
16615     char *sign = "";
16616     static char buf[32];
16617
16618     if (ms > 0 && ms <= 9900) {
16619       /* convert milliseconds to tenths, rounding up */
16620       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16621
16622       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16623       return buf;
16624     }
16625
16626     /* convert milliseconds to seconds, rounding up */
16627     /* use floating point to avoid strangeness of integer division
16628        with negative dividends on many machines */
16629     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16630
16631     if (second < 0) {
16632         sign = "-";
16633         second = -second;
16634     }
16635
16636     day = second / (60 * 60 * 24);
16637     second = second % (60 * 60 * 24);
16638     hour = second / (60 * 60);
16639     second = second % (60 * 60);
16640     minute = second / 60;
16641     second = second % 60;
16642
16643     if (day > 0)
16644       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16645               sign, day, hour, minute, second);
16646     else if (hour > 0)
16647       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16648     else
16649       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16650
16651     return buf;
16652 }
16653
16654
16655 /*
16656  * This is necessary because some C libraries aren't ANSI C compliant yet.
16657  */
16658 char *
16659 StrStr (char *string, char *match)
16660 {
16661     int i, length;
16662
16663     length = strlen(match);
16664
16665     for (i = strlen(string) - length; i >= 0; i--, string++)
16666       if (!strncmp(match, string, length))
16667         return string;
16668
16669     return NULL;
16670 }
16671
16672 char *
16673 StrCaseStr (char *string, char *match)
16674 {
16675     int i, j, length;
16676
16677     length = strlen(match);
16678
16679     for (i = strlen(string) - length; i >= 0; i--, string++) {
16680         for (j = 0; j < length; j++) {
16681             if (ToLower(match[j]) != ToLower(string[j]))
16682               break;
16683         }
16684         if (j == length) return string;
16685     }
16686
16687     return NULL;
16688 }
16689
16690 #ifndef _amigados
16691 int
16692 StrCaseCmp (char *s1, char *s2)
16693 {
16694     char c1, c2;
16695
16696     for (;;) {
16697         c1 = ToLower(*s1++);
16698         c2 = ToLower(*s2++);
16699         if (c1 > c2) return 1;
16700         if (c1 < c2) return -1;
16701         if (c1 == NULLCHAR) return 0;
16702     }
16703 }
16704
16705
16706 int
16707 ToLower (int c)
16708 {
16709     return isupper(c) ? tolower(c) : c;
16710 }
16711
16712
16713 int
16714 ToUpper (int c)
16715 {
16716     return islower(c) ? toupper(c) : c;
16717 }
16718 #endif /* !_amigados    */
16719
16720 char *
16721 StrSave (char *s)
16722 {
16723   char *ret;
16724
16725   if ((ret = (char *) malloc(strlen(s) + 1)))
16726     {
16727       safeStrCpy(ret, s, strlen(s)+1);
16728     }
16729   return ret;
16730 }
16731
16732 char *
16733 StrSavePtr (char *s, char **savePtr)
16734 {
16735     if (*savePtr) {
16736         free(*savePtr);
16737     }
16738     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16739       safeStrCpy(*savePtr, s, strlen(s)+1);
16740     }
16741     return(*savePtr);
16742 }
16743
16744 char *
16745 PGNDate ()
16746 {
16747     time_t clock;
16748     struct tm *tm;
16749     char buf[MSG_SIZ];
16750
16751     clock = time((time_t *)NULL);
16752     tm = localtime(&clock);
16753     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16754             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16755     return StrSave(buf);
16756 }
16757
16758
16759 char *
16760 PositionToFEN (int move, char *overrideCastling)
16761 {
16762     int i, j, fromX, fromY, toX, toY;
16763     int whiteToPlay;
16764     char buf[MSG_SIZ];
16765     char *p, *q;
16766     int emptycount;
16767     ChessSquare piece;
16768
16769     whiteToPlay = (gameMode == EditPosition) ?
16770       !blackPlaysFirst : (move % 2 == 0);
16771     p = buf;
16772
16773     /* Piece placement data */
16774     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16775         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16776         emptycount = 0;
16777         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16778             if (boards[move][i][j] == EmptySquare) {
16779                 emptycount++;
16780             } else { ChessSquare piece = boards[move][i][j];
16781                 if (emptycount > 0) {
16782                     if(emptycount<10) /* [HGM] can be >= 10 */
16783                         *p++ = '0' + emptycount;
16784                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16785                     emptycount = 0;
16786                 }
16787                 if(PieceToChar(piece) == '+') {
16788                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16789                     *p++ = '+';
16790                     piece = (ChessSquare)(DEMOTED piece);
16791                 }
16792                 *p++ = PieceToChar(piece);
16793                 if(p[-1] == '~') {
16794                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16795                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16796                     *p++ = '~';
16797                 }
16798             }
16799         }
16800         if (emptycount > 0) {
16801             if(emptycount<10) /* [HGM] can be >= 10 */
16802                 *p++ = '0' + emptycount;
16803             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16804             emptycount = 0;
16805         }
16806         *p++ = '/';
16807     }
16808     *(p - 1) = ' ';
16809
16810     /* [HGM] print Crazyhouse or Shogi holdings */
16811     if( gameInfo.holdingsWidth ) {
16812         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16813         q = p;
16814         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16815             piece = boards[move][i][BOARD_WIDTH-1];
16816             if( piece != EmptySquare )
16817               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16818                   *p++ = PieceToChar(piece);
16819         }
16820         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16821             piece = boards[move][BOARD_HEIGHT-i-1][0];
16822             if( piece != EmptySquare )
16823               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16824                   *p++ = PieceToChar(piece);
16825         }
16826
16827         if( q == p ) *p++ = '-';
16828         *p++ = ']';
16829         *p++ = ' ';
16830     }
16831
16832     /* Active color */
16833     *p++ = whiteToPlay ? 'w' : 'b';
16834     *p++ = ' ';
16835
16836   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16837     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16838   } else {
16839   if(nrCastlingRights) {
16840      q = p;
16841      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16842        /* [HGM] write directly from rights */
16843            if(boards[move][CASTLING][2] != NoRights &&
16844               boards[move][CASTLING][0] != NoRights   )
16845                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16846            if(boards[move][CASTLING][2] != NoRights &&
16847               boards[move][CASTLING][1] != NoRights   )
16848                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16849            if(boards[move][CASTLING][5] != NoRights &&
16850               boards[move][CASTLING][3] != NoRights   )
16851                 *p++ = boards[move][CASTLING][3] + AAA;
16852            if(boards[move][CASTLING][5] != NoRights &&
16853               boards[move][CASTLING][4] != NoRights   )
16854                 *p++ = boards[move][CASTLING][4] + AAA;
16855      } else {
16856
16857         /* [HGM] write true castling rights */
16858         if( nrCastlingRights == 6 ) {
16859             int q, k=0;
16860             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16861                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16862             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16863                  boards[move][CASTLING][2] != NoRights  );
16864             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16865                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16866                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16867                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16868                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16869             }
16870             if(q) *p++ = 'Q';
16871             k = 0;
16872             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16873                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16874             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16875                  boards[move][CASTLING][5] != NoRights  );
16876             if(gameInfo.variant == VariantSChess) {
16877                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16878                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16879                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16880                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16881             }
16882             if(q) *p++ = 'q';
16883         }
16884      }
16885      if (q == p) *p++ = '-'; /* No castling rights */
16886      *p++ = ' ';
16887   }
16888
16889   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16890      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16891     /* En passant target square */
16892     if (move > backwardMostMove) {
16893         fromX = moveList[move - 1][0] - AAA;
16894         fromY = moveList[move - 1][1] - ONE;
16895         toX = moveList[move - 1][2] - AAA;
16896         toY = moveList[move - 1][3] - ONE;
16897         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16898             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16899             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16900             fromX == toX) {
16901             /* 2-square pawn move just happened */
16902             *p++ = toX + AAA;
16903             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16904         } else {
16905             *p++ = '-';
16906         }
16907     } else if(move == backwardMostMove) {
16908         // [HGM] perhaps we should always do it like this, and forget the above?
16909         if((signed char)boards[move][EP_STATUS] >= 0) {
16910             *p++ = boards[move][EP_STATUS] + AAA;
16911             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16912         } else {
16913             *p++ = '-';
16914         }
16915     } else {
16916         *p++ = '-';
16917     }
16918     *p++ = ' ';
16919   }
16920   }
16921
16922     /* [HGM] find reversible plies */
16923     {   int i = 0, j=move;
16924
16925         if (appData.debugMode) { int k;
16926             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16927             for(k=backwardMostMove; k<=forwardMostMove; k++)
16928                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16929
16930         }
16931
16932         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16933         if( j == backwardMostMove ) i += initialRulePlies;
16934         sprintf(p, "%d ", i);
16935         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16936     }
16937     /* Fullmove number */
16938     sprintf(p, "%d", (move / 2) + 1);
16939
16940     return StrSave(buf);
16941 }
16942
16943 Boolean
16944 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16945 {
16946     int i, j;
16947     char *p, c;
16948     int emptycount, virgin[BOARD_FILES];
16949     ChessSquare piece;
16950
16951     p = fen;
16952
16953     /* [HGM] by default clear Crazyhouse holdings, if present */
16954     if(gameInfo.holdingsWidth) {
16955        for(i=0; i<BOARD_HEIGHT; i++) {
16956            board[i][0]             = EmptySquare; /* black holdings */
16957            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16958            board[i][1]             = (ChessSquare) 0; /* black counts */
16959            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16960        }
16961     }
16962
16963     /* Piece placement data */
16964     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16965         j = 0;
16966         for (;;) {
16967             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16968                 if (*p == '/') p++;
16969                 emptycount = gameInfo.boardWidth - j;
16970                 while (emptycount--)
16971                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16972                 break;
16973 #if(BOARD_FILES >= 10)
16974             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16975                 p++; emptycount=10;
16976                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16977                 while (emptycount--)
16978                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16979 #endif
16980             } else if (isdigit(*p)) {
16981                 emptycount = *p++ - '0';
16982                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16983                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16984                 while (emptycount--)
16985                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16986             } else if (*p == '+' || isalpha(*p)) {
16987                 if (j >= gameInfo.boardWidth) return FALSE;
16988                 if(*p=='+') {
16989                     piece = CharToPiece(*++p);
16990                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16991                     piece = (ChessSquare) (PROMOTED piece ); p++;
16992                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16993                 } else piece = CharToPiece(*p++);
16994
16995                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16996                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16997                     piece = (ChessSquare) (PROMOTED piece);
16998                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16999                     p++;
17000                 }
17001                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17002             } else {
17003                 return FALSE;
17004             }
17005         }
17006     }
17007     while (*p == '/' || *p == ' ') p++;
17008
17009     /* [HGM] look for Crazyhouse holdings here */
17010     while(*p==' ') p++;
17011     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17012         if(*p == '[') p++;
17013         if(*p == '-' ) p++; /* empty holdings */ else {
17014             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17015             /* if we would allow FEN reading to set board size, we would   */
17016             /* have to add holdings and shift the board read so far here   */
17017             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17018                 p++;
17019                 if((int) piece >= (int) BlackPawn ) {
17020                     i = (int)piece - (int)BlackPawn;
17021                     i = PieceToNumber((ChessSquare)i);
17022                     if( i >= gameInfo.holdingsSize ) return FALSE;
17023                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17024                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17025                 } else {
17026                     i = (int)piece - (int)WhitePawn;
17027                     i = PieceToNumber((ChessSquare)i);
17028                     if( i >= gameInfo.holdingsSize ) return FALSE;
17029                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17030                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17031                 }
17032             }
17033         }
17034         if(*p == ']') p++;
17035     }
17036
17037     while(*p == ' ') p++;
17038
17039     /* Active color */
17040     c = *p++;
17041     if(appData.colorNickNames) {
17042       if( c == appData.colorNickNames[0] ) c = 'w'; else
17043       if( c == appData.colorNickNames[1] ) c = 'b';
17044     }
17045     switch (c) {
17046       case 'w':
17047         *blackPlaysFirst = FALSE;
17048         break;
17049       case 'b':
17050         *blackPlaysFirst = TRUE;
17051         break;
17052       default:
17053         return FALSE;
17054     }
17055
17056     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17057     /* return the extra info in global variiables             */
17058
17059     /* set defaults in case FEN is incomplete */
17060     board[EP_STATUS] = EP_UNKNOWN;
17061     for(i=0; i<nrCastlingRights; i++ ) {
17062         board[CASTLING][i] =
17063             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17064     }   /* assume possible unless obviously impossible */
17065     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17066     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17067     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17068                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17069     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17070     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17071     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17072                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17073     FENrulePlies = 0;
17074
17075     while(*p==' ') p++;
17076     if(nrCastlingRights) {
17077       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17078       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17079           /* castling indicator present, so default becomes no castlings */
17080           for(i=0; i<nrCastlingRights; i++ ) {
17081                  board[CASTLING][i] = NoRights;
17082           }
17083       }
17084       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17085              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17086              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17087              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17088         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17089
17090         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17091             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17092             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17093         }
17094         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17095             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17096         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17097                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17098         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17099                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17100         switch(c) {
17101           case'K':
17102               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17103               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17104               board[CASTLING][2] = whiteKingFile;
17105               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17106               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17107               break;
17108           case'Q':
17109               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17110               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17111               board[CASTLING][2] = whiteKingFile;
17112               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17113               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17114               break;
17115           case'k':
17116               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17117               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17118               board[CASTLING][5] = blackKingFile;
17119               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17120               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17121               break;
17122           case'q':
17123               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17124               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17125               board[CASTLING][5] = blackKingFile;
17126               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17127               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17128           case '-':
17129               break;
17130           default: /* FRC castlings */
17131               if(c >= 'a') { /* black rights */
17132                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17133                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17134                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17135                   if(i == BOARD_RGHT) break;
17136                   board[CASTLING][5] = i;
17137                   c -= AAA;
17138                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17139                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17140                   if(c > i)
17141                       board[CASTLING][3] = c;
17142                   else
17143                       board[CASTLING][4] = c;
17144               } else { /* white rights */
17145                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17146                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17147                     if(board[0][i] == WhiteKing) break;
17148                   if(i == BOARD_RGHT) break;
17149                   board[CASTLING][2] = i;
17150                   c -= AAA - 'a' + 'A';
17151                   if(board[0][c] >= WhiteKing) break;
17152                   if(c > i)
17153                       board[CASTLING][0] = c;
17154                   else
17155                       board[CASTLING][1] = c;
17156               }
17157         }
17158       }
17159       for(i=0; i<nrCastlingRights; i++)
17160         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17161       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17162     if (appData.debugMode) {
17163         fprintf(debugFP, "FEN castling rights:");
17164         for(i=0; i<nrCastlingRights; i++)
17165         fprintf(debugFP, " %d", board[CASTLING][i]);
17166         fprintf(debugFP, "\n");
17167     }
17168
17169       while(*p==' ') p++;
17170     }
17171
17172     /* read e.p. field in games that know e.p. capture */
17173     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17174        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17175       if(*p=='-') {
17176         p++; board[EP_STATUS] = EP_NONE;
17177       } else {
17178          char c = *p++ - AAA;
17179
17180          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17181          if(*p >= '0' && *p <='9') p++;
17182          board[EP_STATUS] = c;
17183       }
17184     }
17185
17186
17187     if(sscanf(p, "%d", &i) == 1) {
17188         FENrulePlies = i; /* 50-move ply counter */
17189         /* (The move number is still ignored)    */
17190     }
17191
17192     return TRUE;
17193 }
17194
17195 void
17196 EditPositionPasteFEN (char *fen)
17197 {
17198   if (fen != NULL) {
17199     Board initial_position;
17200
17201     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17202       DisplayError(_("Bad FEN position in clipboard"), 0);
17203       return ;
17204     } else {
17205       int savedBlackPlaysFirst = blackPlaysFirst;
17206       EditPositionEvent();
17207       blackPlaysFirst = savedBlackPlaysFirst;
17208       CopyBoard(boards[0], initial_position);
17209       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17210       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17211       DisplayBothClocks();
17212       DrawPosition(FALSE, boards[currentMove]);
17213     }
17214   }
17215 }
17216
17217 static char cseq[12] = "\\   ";
17218
17219 Boolean
17220 set_cont_sequence (char *new_seq)
17221 {
17222     int len;
17223     Boolean ret;
17224
17225     // handle bad attempts to set the sequence
17226         if (!new_seq)
17227                 return 0; // acceptable error - no debug
17228
17229     len = strlen(new_seq);
17230     ret = (len > 0) && (len < sizeof(cseq));
17231     if (ret)
17232       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17233     else if (appData.debugMode)
17234       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17235     return ret;
17236 }
17237
17238 /*
17239     reformat a source message so words don't cross the width boundary.  internal
17240     newlines are not removed.  returns the wrapped size (no null character unless
17241     included in source message).  If dest is NULL, only calculate the size required
17242     for the dest buffer.  lp argument indicats line position upon entry, and it's
17243     passed back upon exit.
17244 */
17245 int
17246 wrap (char *dest, char *src, int count, int width, int *lp)
17247 {
17248     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17249
17250     cseq_len = strlen(cseq);
17251     old_line = line = *lp;
17252     ansi = len = clen = 0;
17253
17254     for (i=0; i < count; i++)
17255     {
17256         if (src[i] == '\033')
17257             ansi = 1;
17258
17259         // if we hit the width, back up
17260         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17261         {
17262             // store i & len in case the word is too long
17263             old_i = i, old_len = len;
17264
17265             // find the end of the last word
17266             while (i && src[i] != ' ' && src[i] != '\n')
17267             {
17268                 i--;
17269                 len--;
17270             }
17271
17272             // word too long?  restore i & len before splitting it
17273             if ((old_i-i+clen) >= width)
17274             {
17275                 i = old_i;
17276                 len = old_len;
17277             }
17278
17279             // extra space?
17280             if (i && src[i-1] == ' ')
17281                 len--;
17282
17283             if (src[i] != ' ' && src[i] != '\n')
17284             {
17285                 i--;
17286                 if (len)
17287                     len--;
17288             }
17289
17290             // now append the newline and continuation sequence
17291             if (dest)
17292                 dest[len] = '\n';
17293             len++;
17294             if (dest)
17295                 strncpy(dest+len, cseq, cseq_len);
17296             len += cseq_len;
17297             line = cseq_len;
17298             clen = cseq_len;
17299             continue;
17300         }
17301
17302         if (dest)
17303             dest[len] = src[i];
17304         len++;
17305         if (!ansi)
17306             line++;
17307         if (src[i] == '\n')
17308             line = 0;
17309         if (src[i] == 'm')
17310             ansi = 0;
17311     }
17312     if (dest && appData.debugMode)
17313     {
17314         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17315             count, width, line, len, *lp);
17316         show_bytes(debugFP, src, count);
17317         fprintf(debugFP, "\ndest: ");
17318         show_bytes(debugFP, dest, len);
17319         fprintf(debugFP, "\n");
17320     }
17321     *lp = dest ? line : old_line;
17322
17323     return len;
17324 }
17325
17326 // [HGM] vari: routines for shelving variations
17327 Boolean modeRestore = FALSE;
17328
17329 void
17330 PushInner (int firstMove, int lastMove)
17331 {
17332         int i, j, nrMoves = lastMove - firstMove;
17333
17334         // push current tail of game on stack
17335         savedResult[storedGames] = gameInfo.result;
17336         savedDetails[storedGames] = gameInfo.resultDetails;
17337         gameInfo.resultDetails = NULL;
17338         savedFirst[storedGames] = firstMove;
17339         savedLast [storedGames] = lastMove;
17340         savedFramePtr[storedGames] = framePtr;
17341         framePtr -= nrMoves; // reserve space for the boards
17342         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17343             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17344             for(j=0; j<MOVE_LEN; j++)
17345                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17346             for(j=0; j<2*MOVE_LEN; j++)
17347                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17348             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17349             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17350             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17351             pvInfoList[firstMove+i-1].depth = 0;
17352             commentList[framePtr+i] = commentList[firstMove+i];
17353             commentList[firstMove+i] = NULL;
17354         }
17355
17356         storedGames++;
17357         forwardMostMove = firstMove; // truncate game so we can start variation
17358 }
17359
17360 void
17361 PushTail (int firstMove, int lastMove)
17362 {
17363         if(appData.icsActive) { // only in local mode
17364                 forwardMostMove = currentMove; // mimic old ICS behavior
17365                 return;
17366         }
17367         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17368
17369         PushInner(firstMove, lastMove);
17370         if(storedGames == 1) GreyRevert(FALSE);
17371         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17372 }
17373
17374 void
17375 PopInner (Boolean annotate)
17376 {
17377         int i, j, nrMoves;
17378         char buf[8000], moveBuf[20];
17379
17380         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17381         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17382         nrMoves = savedLast[storedGames] - currentMove;
17383         if(annotate) {
17384                 int cnt = 10;
17385                 if(!WhiteOnMove(currentMove))
17386                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17387                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17388                 for(i=currentMove; i<forwardMostMove; i++) {
17389                         if(WhiteOnMove(i))
17390                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17391                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17392                         strcat(buf, moveBuf);
17393                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17394                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17395                 }
17396                 strcat(buf, ")");
17397         }
17398         for(i=1; i<=nrMoves; i++) { // copy last variation back
17399             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17400             for(j=0; j<MOVE_LEN; j++)
17401                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17402             for(j=0; j<2*MOVE_LEN; j++)
17403                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17404             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17405             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17406             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17407             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17408             commentList[currentMove+i] = commentList[framePtr+i];
17409             commentList[framePtr+i] = NULL;
17410         }
17411         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17412         framePtr = savedFramePtr[storedGames];
17413         gameInfo.result = savedResult[storedGames];
17414         if(gameInfo.resultDetails != NULL) {
17415             free(gameInfo.resultDetails);
17416       }
17417         gameInfo.resultDetails = savedDetails[storedGames];
17418         forwardMostMove = currentMove + nrMoves;
17419 }
17420
17421 Boolean
17422 PopTail (Boolean annotate)
17423 {
17424         if(appData.icsActive) return FALSE; // only in local mode
17425         if(!storedGames) return FALSE; // sanity
17426         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17427
17428         PopInner(annotate);
17429         if(currentMove < forwardMostMove) ForwardEvent(); else
17430         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17431
17432         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17433         return TRUE;
17434 }
17435
17436 void
17437 CleanupTail ()
17438 {       // remove all shelved variations
17439         int i;
17440         for(i=0; i<storedGames; i++) {
17441             if(savedDetails[i])
17442                 free(savedDetails[i]);
17443             savedDetails[i] = NULL;
17444         }
17445         for(i=framePtr; i<MAX_MOVES; i++) {
17446                 if(commentList[i]) free(commentList[i]);
17447                 commentList[i] = NULL;
17448         }
17449         framePtr = MAX_MOVES-1;
17450         storedGames = 0;
17451 }
17452
17453 void
17454 LoadVariation (int index, char *text)
17455 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17456         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17457         int level = 0, move;
17458
17459         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17460         // first find outermost bracketing variation
17461         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17462             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17463                 if(*p == '{') wait = '}'; else
17464                 if(*p == '[') wait = ']'; else
17465                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17466                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17467             }
17468             if(*p == wait) wait = NULLCHAR; // closing ]} found
17469             p++;
17470         }
17471         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17472         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17473         end[1] = NULLCHAR; // clip off comment beyond variation
17474         ToNrEvent(currentMove-1);
17475         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17476         // kludge: use ParsePV() to append variation to game
17477         move = currentMove;
17478         ParsePV(start, TRUE, TRUE);
17479         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17480         ClearPremoveHighlights();
17481         CommentPopDown();
17482         ToNrEvent(currentMove+1);
17483 }
17484