b7dad0656ff2d4424512794f4dd4c5051335f70b
[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 void
13449 AnalyzeModeEvent ()
13450 {
13451     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13452     if (appData.noChessProgram || gameMode == AnalyzeMode)
13453       return;
13454
13455     if (gameMode != AnalyzeFile) {
13456         if (!appData.icsEngineAnalyze) {
13457                EditGameEvent();
13458                if (gameMode != EditGame) return;
13459         }
13460         ResurrectChessProgram();
13461         SendToProgram("analyze\n", &first);
13462         first.analyzing = TRUE;
13463         /*first.maybeThinking = TRUE;*/
13464         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13465         EngineOutputPopUp();
13466     }
13467     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13468     pausing = FALSE;
13469     ModeHighlight();
13470     SetGameInfo();
13471
13472     StartAnalysisClock();
13473     GetTimeMark(&lastNodeCountTime);
13474     lastNodeCount = 0;
13475 }
13476
13477 void
13478 AnalyzeFileEvent ()
13479 {
13480     if (appData.noChessProgram || gameMode == AnalyzeFile)
13481       return;
13482
13483     if (gameMode != AnalyzeMode) {
13484         EditGameEvent();
13485         if (gameMode != EditGame) return;
13486         ResurrectChessProgram();
13487         SendToProgram("analyze\n", &first);
13488         first.analyzing = TRUE;
13489         /*first.maybeThinking = TRUE;*/
13490         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13491         EngineOutputPopUp();
13492     }
13493     gameMode = AnalyzeFile;
13494     pausing = FALSE;
13495     ModeHighlight();
13496     SetGameInfo();
13497
13498     StartAnalysisClock();
13499     GetTimeMark(&lastNodeCountTime);
13500     lastNodeCount = 0;
13501     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13502 }
13503
13504 void
13505 MachineWhiteEvent ()
13506 {
13507     char buf[MSG_SIZ];
13508     char *bookHit = NULL;
13509
13510     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13511       return;
13512
13513
13514     if (gameMode == PlayFromGameFile ||
13515         gameMode == TwoMachinesPlay  ||
13516         gameMode == Training         ||
13517         gameMode == AnalyzeMode      ||
13518         gameMode == EndOfGame)
13519         EditGameEvent();
13520
13521     if (gameMode == EditPosition)
13522         EditPositionDone(TRUE);
13523
13524     if (!WhiteOnMove(currentMove)) {
13525         DisplayError(_("It is not White's turn"), 0);
13526         return;
13527     }
13528
13529     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13530       ExitAnalyzeMode();
13531
13532     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13533         gameMode == AnalyzeFile)
13534         TruncateGame();
13535
13536     ResurrectChessProgram();    /* in case it isn't running */
13537     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13538         gameMode = MachinePlaysWhite;
13539         ResetClocks();
13540     } else
13541     gameMode = MachinePlaysWhite;
13542     pausing = FALSE;
13543     ModeHighlight();
13544     SetGameInfo();
13545     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13546     DisplayTitle(buf);
13547     if (first.sendName) {
13548       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13549       SendToProgram(buf, &first);
13550     }
13551     if (first.sendTime) {
13552       if (first.useColors) {
13553         SendToProgram("black\n", &first); /*gnu kludge*/
13554       }
13555       SendTimeRemaining(&first, TRUE);
13556     }
13557     if (first.useColors) {
13558       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13559     }
13560     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13561     SetMachineThinkingEnables();
13562     first.maybeThinking = TRUE;
13563     StartClocks();
13564     firstMove = FALSE;
13565
13566     if (appData.autoFlipView && !flipView) {
13567       flipView = !flipView;
13568       DrawPosition(FALSE, NULL);
13569       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13570     }
13571
13572     if(bookHit) { // [HGM] book: simulate book reply
13573         static char bookMove[MSG_SIZ]; // a bit generous?
13574
13575         programStats.nodes = programStats.depth = programStats.time =
13576         programStats.score = programStats.got_only_move = 0;
13577         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13578
13579         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13580         strcat(bookMove, bookHit);
13581         HandleMachineMove(bookMove, &first);
13582     }
13583 }
13584
13585 void
13586 MachineBlackEvent ()
13587 {
13588   char buf[MSG_SIZ];
13589   char *bookHit = NULL;
13590
13591     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13592         return;
13593
13594
13595     if (gameMode == PlayFromGameFile ||
13596         gameMode == TwoMachinesPlay  ||
13597         gameMode == Training         ||
13598         gameMode == AnalyzeMode      ||
13599         gameMode == EndOfGame)
13600         EditGameEvent();
13601
13602     if (gameMode == EditPosition)
13603         EditPositionDone(TRUE);
13604
13605     if (WhiteOnMove(currentMove)) {
13606         DisplayError(_("It is not Black's turn"), 0);
13607         return;
13608     }
13609
13610     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13611       ExitAnalyzeMode();
13612
13613     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13614         gameMode == AnalyzeFile)
13615         TruncateGame();
13616
13617     ResurrectChessProgram();    /* in case it isn't running */
13618     gameMode = MachinePlaysBlack;
13619     pausing = FALSE;
13620     ModeHighlight();
13621     SetGameInfo();
13622     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13623     DisplayTitle(buf);
13624     if (first.sendName) {
13625       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13626       SendToProgram(buf, &first);
13627     }
13628     if (first.sendTime) {
13629       if (first.useColors) {
13630         SendToProgram("white\n", &first); /*gnu kludge*/
13631       }
13632       SendTimeRemaining(&first, FALSE);
13633     }
13634     if (first.useColors) {
13635       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13636     }
13637     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13638     SetMachineThinkingEnables();
13639     first.maybeThinking = TRUE;
13640     StartClocks();
13641
13642     if (appData.autoFlipView && flipView) {
13643       flipView = !flipView;
13644       DrawPosition(FALSE, NULL);
13645       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13646     }
13647     if(bookHit) { // [HGM] book: simulate book reply
13648         static char bookMove[MSG_SIZ]; // a bit generous?
13649
13650         programStats.nodes = programStats.depth = programStats.time =
13651         programStats.score = programStats.got_only_move = 0;
13652         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13653
13654         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13655         strcat(bookMove, bookHit);
13656         HandleMachineMove(bookMove, &first);
13657     }
13658 }
13659
13660
13661 void
13662 DisplayTwoMachinesTitle ()
13663 {
13664     char buf[MSG_SIZ];
13665     if (appData.matchGames > 0) {
13666         if(appData.tourneyFile[0]) {
13667           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13668                    gameInfo.white, _("vs."), gameInfo.black,
13669                    nextGame+1, appData.matchGames+1,
13670                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13671         } else 
13672         if (first.twoMachinesColor[0] == 'w') {
13673           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13674                    gameInfo.white, _("vs."),  gameInfo.black,
13675                    first.matchWins, second.matchWins,
13676                    matchGame - 1 - (first.matchWins + second.matchWins));
13677         } else {
13678           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13679                    gameInfo.white, _("vs."), gameInfo.black,
13680                    second.matchWins, first.matchWins,
13681                    matchGame - 1 - (first.matchWins + second.matchWins));
13682         }
13683     } else {
13684       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13685     }
13686     DisplayTitle(buf);
13687 }
13688
13689 void
13690 SettingsMenuIfReady ()
13691 {
13692   if (second.lastPing != second.lastPong) {
13693     DisplayMessage("", _("Waiting for second chess program"));
13694     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13695     return;
13696   }
13697   ThawUI();
13698   DisplayMessage("", "");
13699   SettingsPopUp(&second);
13700 }
13701
13702 int
13703 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13704 {
13705     char buf[MSG_SIZ];
13706     if (cps->pr == NoProc) {
13707         StartChessProgram(cps);
13708         if (cps->protocolVersion == 1) {
13709           retry();
13710         } else {
13711           /* kludge: allow timeout for initial "feature" command */
13712           FreezeUI();
13713           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13714           DisplayMessage("", buf);
13715           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13716         }
13717         return 1;
13718     }
13719     return 0;
13720 }
13721
13722 void
13723 TwoMachinesEvent P((void))
13724 {
13725     int i;
13726     char buf[MSG_SIZ];
13727     ChessProgramState *onmove;
13728     char *bookHit = NULL;
13729     static int stalling = 0;
13730     TimeMark now;
13731     long wait;
13732
13733     if (appData.noChessProgram) return;
13734
13735     switch (gameMode) {
13736       case TwoMachinesPlay:
13737         return;
13738       case MachinePlaysWhite:
13739       case MachinePlaysBlack:
13740         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13741             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13742             return;
13743         }
13744         /* fall through */
13745       case BeginningOfGame:
13746       case PlayFromGameFile:
13747       case EndOfGame:
13748         EditGameEvent();
13749         if (gameMode != EditGame) return;
13750         break;
13751       case EditPosition:
13752         EditPositionDone(TRUE);
13753         break;
13754       case AnalyzeMode:
13755       case AnalyzeFile:
13756         ExitAnalyzeMode();
13757         break;
13758       case EditGame:
13759       default:
13760         break;
13761     }
13762
13763 //    forwardMostMove = currentMove;
13764     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13765
13766     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13767
13768     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13769     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13770       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13771       return;
13772     }
13773
13774     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13775         DisplayError("second engine does not play this", 0);
13776         return;
13777     }
13778
13779     if(!stalling) {
13780       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13781       SendToProgram("force\n", &second);
13782       stalling = 1;
13783       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13784       return;
13785     }
13786     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13787     if(appData.matchPause>10000 || appData.matchPause<10)
13788                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13789     wait = SubtractTimeMarks(&now, &pauseStart);
13790     if(wait < appData.matchPause) {
13791         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13792         return;
13793     }
13794     // we are now committed to starting the game
13795     stalling = 0;
13796     DisplayMessage("", "");
13797     if (startedFromSetupPosition) {
13798         SendBoard(&second, backwardMostMove);
13799     if (appData.debugMode) {
13800         fprintf(debugFP, "Two Machines\n");
13801     }
13802     }
13803     for (i = backwardMostMove; i < forwardMostMove; i++) {
13804         SendMoveToProgram(i, &second);
13805     }
13806
13807     gameMode = TwoMachinesPlay;
13808     pausing = FALSE;
13809     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13810     SetGameInfo();
13811     DisplayTwoMachinesTitle();
13812     firstMove = TRUE;
13813     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13814         onmove = &first;
13815     } else {
13816         onmove = &second;
13817     }
13818     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13819     SendToProgram(first.computerString, &first);
13820     if (first.sendName) {
13821       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13822       SendToProgram(buf, &first);
13823     }
13824     SendToProgram(second.computerString, &second);
13825     if (second.sendName) {
13826       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13827       SendToProgram(buf, &second);
13828     }
13829
13830     ResetClocks();
13831     if (!first.sendTime || !second.sendTime) {
13832         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13833         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13834     }
13835     if (onmove->sendTime) {
13836       if (onmove->useColors) {
13837         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13838       }
13839       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13840     }
13841     if (onmove->useColors) {
13842       SendToProgram(onmove->twoMachinesColor, onmove);
13843     }
13844     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13845 //    SendToProgram("go\n", onmove);
13846     onmove->maybeThinking = TRUE;
13847     SetMachineThinkingEnables();
13848
13849     StartClocks();
13850
13851     if(bookHit) { // [HGM] book: simulate book reply
13852         static char bookMove[MSG_SIZ]; // a bit generous?
13853
13854         programStats.nodes = programStats.depth = programStats.time =
13855         programStats.score = programStats.got_only_move = 0;
13856         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13857
13858         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13859         strcat(bookMove, bookHit);
13860         savedMessage = bookMove; // args for deferred call
13861         savedState = onmove;
13862         ScheduleDelayedEvent(DeferredBookMove, 1);
13863     }
13864 }
13865
13866 void
13867 TrainingEvent ()
13868 {
13869     if (gameMode == Training) {
13870       SetTrainingModeOff();
13871       gameMode = PlayFromGameFile;
13872       DisplayMessage("", _("Training mode off"));
13873     } else {
13874       gameMode = Training;
13875       animateTraining = appData.animate;
13876
13877       /* make sure we are not already at the end of the game */
13878       if (currentMove < forwardMostMove) {
13879         SetTrainingModeOn();
13880         DisplayMessage("", _("Training mode on"));
13881       } else {
13882         gameMode = PlayFromGameFile;
13883         DisplayError(_("Already at end of game"), 0);
13884       }
13885     }
13886     ModeHighlight();
13887 }
13888
13889 void
13890 IcsClientEvent ()
13891 {
13892     if (!appData.icsActive) return;
13893     switch (gameMode) {
13894       case IcsPlayingWhite:
13895       case IcsPlayingBlack:
13896       case IcsObserving:
13897       case IcsIdle:
13898       case BeginningOfGame:
13899       case IcsExamining:
13900         return;
13901
13902       case EditGame:
13903         break;
13904
13905       case EditPosition:
13906         EditPositionDone(TRUE);
13907         break;
13908
13909       case AnalyzeMode:
13910       case AnalyzeFile:
13911         ExitAnalyzeMode();
13912         break;
13913
13914       default:
13915         EditGameEvent();
13916         break;
13917     }
13918
13919     gameMode = IcsIdle;
13920     ModeHighlight();
13921     return;
13922 }
13923
13924 void
13925 EditGameEvent ()
13926 {
13927     int i;
13928
13929     switch (gameMode) {
13930       case Training:
13931         SetTrainingModeOff();
13932         break;
13933       case MachinePlaysWhite:
13934       case MachinePlaysBlack:
13935       case BeginningOfGame:
13936         SendToProgram("force\n", &first);
13937         SetUserThinkingEnables();
13938         break;
13939       case PlayFromGameFile:
13940         (void) StopLoadGameTimer();
13941         if (gameFileFP != NULL) {
13942             gameFileFP = NULL;
13943         }
13944         break;
13945       case EditPosition:
13946         EditPositionDone(TRUE);
13947         break;
13948       case AnalyzeMode:
13949       case AnalyzeFile:
13950         ExitAnalyzeMode();
13951         SendToProgram("force\n", &first);
13952         break;
13953       case TwoMachinesPlay:
13954         GameEnds(EndOfFile, NULL, GE_PLAYER);
13955         ResurrectChessProgram();
13956         SetUserThinkingEnables();
13957         break;
13958       case EndOfGame:
13959         ResurrectChessProgram();
13960         break;
13961       case IcsPlayingBlack:
13962       case IcsPlayingWhite:
13963         DisplayError(_("Warning: You are still playing a game"), 0);
13964         break;
13965       case IcsObserving:
13966         DisplayError(_("Warning: You are still observing a game"), 0);
13967         break;
13968       case IcsExamining:
13969         DisplayError(_("Warning: You are still examining a game"), 0);
13970         break;
13971       case IcsIdle:
13972         break;
13973       case EditGame:
13974       default:
13975         return;
13976     }
13977
13978     pausing = FALSE;
13979     StopClocks();
13980     first.offeredDraw = second.offeredDraw = 0;
13981
13982     if (gameMode == PlayFromGameFile) {
13983         whiteTimeRemaining = timeRemaining[0][currentMove];
13984         blackTimeRemaining = timeRemaining[1][currentMove];
13985         DisplayTitle("");
13986     }
13987
13988     if (gameMode == MachinePlaysWhite ||
13989         gameMode == MachinePlaysBlack ||
13990         gameMode == TwoMachinesPlay ||
13991         gameMode == EndOfGame) {
13992         i = forwardMostMove;
13993         while (i > currentMove) {
13994             SendToProgram("undo\n", &first);
13995             i--;
13996         }
13997         if(!adjustedClock) {
13998         whiteTimeRemaining = timeRemaining[0][currentMove];
13999         blackTimeRemaining = timeRemaining[1][currentMove];
14000         DisplayBothClocks();
14001         }
14002         if (whiteFlag || blackFlag) {
14003             whiteFlag = blackFlag = 0;
14004         }
14005         DisplayTitle("");
14006     }
14007
14008     gameMode = EditGame;
14009     ModeHighlight();
14010     SetGameInfo();
14011 }
14012
14013
14014 void
14015 EditPositionEvent ()
14016 {
14017     if (gameMode == EditPosition) {
14018         EditGameEvent();
14019         return;
14020     }
14021
14022     EditGameEvent();
14023     if (gameMode != EditGame) return;
14024
14025     gameMode = EditPosition;
14026     ModeHighlight();
14027     SetGameInfo();
14028     if (currentMove > 0)
14029       CopyBoard(boards[0], boards[currentMove]);
14030
14031     blackPlaysFirst = !WhiteOnMove(currentMove);
14032     ResetClocks();
14033     currentMove = forwardMostMove = backwardMostMove = 0;
14034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14035     DisplayMove(-1);
14036     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14037 }
14038
14039 void
14040 ExitAnalyzeMode ()
14041 {
14042     /* [DM] icsEngineAnalyze - possible call from other functions */
14043     if (appData.icsEngineAnalyze) {
14044         appData.icsEngineAnalyze = FALSE;
14045
14046         DisplayMessage("",_("Close ICS engine analyze..."));
14047     }
14048     if (first.analysisSupport && first.analyzing) {
14049       SendToBoth("exit\n");
14050       first.analyzing = second.analyzing = FALSE;
14051     }
14052     thinkOutput[0] = NULLCHAR;
14053 }
14054
14055 void
14056 EditPositionDone (Boolean fakeRights)
14057 {
14058     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14059
14060     startedFromSetupPosition = TRUE;
14061     InitChessProgram(&first, FALSE);
14062     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14063       boards[0][EP_STATUS] = EP_NONE;
14064       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14065       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14066         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14067         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14068       } else boards[0][CASTLING][2] = NoRights;
14069       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14070         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14071         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14072       } else boards[0][CASTLING][5] = NoRights;
14073       if(gameInfo.variant == VariantSChess) {
14074         int i;
14075         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14076           boards[0][VIRGIN][i] = 0;
14077           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14078           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14079         }
14080       }
14081     }
14082     SendToProgram("force\n", &first);
14083     if (blackPlaysFirst) {
14084         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14085         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14086         currentMove = forwardMostMove = backwardMostMove = 1;
14087         CopyBoard(boards[1], boards[0]);
14088     } else {
14089         currentMove = forwardMostMove = backwardMostMove = 0;
14090     }
14091     SendBoard(&first, forwardMostMove);
14092     if (appData.debugMode) {
14093         fprintf(debugFP, "EditPosDone\n");
14094     }
14095     DisplayTitle("");
14096     DisplayMessage("", "");
14097     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14098     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14099     gameMode = EditGame;
14100     ModeHighlight();
14101     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14102     ClearHighlights(); /* [AS] */
14103 }
14104
14105 /* Pause for `ms' milliseconds */
14106 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14107 void
14108 TimeDelay (long ms)
14109 {
14110     TimeMark m1, m2;
14111
14112     GetTimeMark(&m1);
14113     do {
14114         GetTimeMark(&m2);
14115     } while (SubtractTimeMarks(&m2, &m1) < ms);
14116 }
14117
14118 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14119 void
14120 SendMultiLineToICS (char *buf)
14121 {
14122     char temp[MSG_SIZ+1], *p;
14123     int len;
14124
14125     len = strlen(buf);
14126     if (len > MSG_SIZ)
14127       len = MSG_SIZ;
14128
14129     strncpy(temp, buf, len);
14130     temp[len] = 0;
14131
14132     p = temp;
14133     while (*p) {
14134         if (*p == '\n' || *p == '\r')
14135           *p = ' ';
14136         ++p;
14137     }
14138
14139     strcat(temp, "\n");
14140     SendToICS(temp);
14141     SendToPlayer(temp, strlen(temp));
14142 }
14143
14144 void
14145 SetWhiteToPlayEvent ()
14146 {
14147     if (gameMode == EditPosition) {
14148         blackPlaysFirst = FALSE;
14149         DisplayBothClocks();    /* works because currentMove is 0 */
14150     } else if (gameMode == IcsExamining) {
14151         SendToICS(ics_prefix);
14152         SendToICS("tomove white\n");
14153     }
14154 }
14155
14156 void
14157 SetBlackToPlayEvent ()
14158 {
14159     if (gameMode == EditPosition) {
14160         blackPlaysFirst = TRUE;
14161         currentMove = 1;        /* kludge */
14162         DisplayBothClocks();
14163         currentMove = 0;
14164     } else if (gameMode == IcsExamining) {
14165         SendToICS(ics_prefix);
14166         SendToICS("tomove black\n");
14167     }
14168 }
14169
14170 void
14171 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14172 {
14173     char buf[MSG_SIZ];
14174     ChessSquare piece = boards[0][y][x];
14175
14176     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14177
14178     switch (selection) {
14179       case ClearBoard:
14180         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14181             SendToICS(ics_prefix);
14182             SendToICS("bsetup clear\n");
14183         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14184             SendToICS(ics_prefix);
14185             SendToICS("clearboard\n");
14186         } else {
14187             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14188                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14189                 for (y = 0; y < BOARD_HEIGHT; y++) {
14190                     if (gameMode == IcsExamining) {
14191                         if (boards[currentMove][y][x] != EmptySquare) {
14192                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14193                                     AAA + x, ONE + y);
14194                             SendToICS(buf);
14195                         }
14196                     } else {
14197                         boards[0][y][x] = p;
14198                     }
14199                 }
14200             }
14201         }
14202         if (gameMode == EditPosition) {
14203             DrawPosition(FALSE, boards[0]);
14204         }
14205         break;
14206
14207       case WhitePlay:
14208         SetWhiteToPlayEvent();
14209         break;
14210
14211       case BlackPlay:
14212         SetBlackToPlayEvent();
14213         break;
14214
14215       case EmptySquare:
14216         if (gameMode == IcsExamining) {
14217             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14218             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14219             SendToICS(buf);
14220         } else {
14221             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14222                 if(x == BOARD_LEFT-2) {
14223                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14224                     boards[0][y][1] = 0;
14225                 } else
14226                 if(x == BOARD_RGHT+1) {
14227                     if(y >= gameInfo.holdingsSize) break;
14228                     boards[0][y][BOARD_WIDTH-2] = 0;
14229                 } else break;
14230             }
14231             boards[0][y][x] = EmptySquare;
14232             DrawPosition(FALSE, boards[0]);
14233         }
14234         break;
14235
14236       case PromotePiece:
14237         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14238            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14239             selection = (ChessSquare) (PROMOTED piece);
14240         } else if(piece == EmptySquare) selection = WhiteSilver;
14241         else selection = (ChessSquare)((int)piece - 1);
14242         goto defaultlabel;
14243
14244       case DemotePiece:
14245         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14246            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14247             selection = (ChessSquare) (DEMOTED piece);
14248         } else if(piece == EmptySquare) selection = BlackSilver;
14249         else selection = (ChessSquare)((int)piece + 1);
14250         goto defaultlabel;
14251
14252       case WhiteQueen:
14253       case BlackQueen:
14254         if(gameInfo.variant == VariantShatranj ||
14255            gameInfo.variant == VariantXiangqi  ||
14256            gameInfo.variant == VariantCourier  ||
14257            gameInfo.variant == VariantMakruk     )
14258             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14259         goto defaultlabel;
14260
14261       case WhiteKing:
14262       case BlackKing:
14263         if(gameInfo.variant == VariantXiangqi)
14264             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14265         if(gameInfo.variant == VariantKnightmate)
14266             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14267       default:
14268         defaultlabel:
14269         if (gameMode == IcsExamining) {
14270             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14271             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14272                      PieceToChar(selection), AAA + x, ONE + y);
14273             SendToICS(buf);
14274         } else {
14275             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14276                 int n;
14277                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14278                     n = PieceToNumber(selection - BlackPawn);
14279                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14280                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14281                     boards[0][BOARD_HEIGHT-1-n][1]++;
14282                 } else
14283                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14284                     n = PieceToNumber(selection);
14285                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14286                     boards[0][n][BOARD_WIDTH-1] = selection;
14287                     boards[0][n][BOARD_WIDTH-2]++;
14288                 }
14289             } else
14290             boards[0][y][x] = selection;
14291             DrawPosition(TRUE, boards[0]);
14292             ClearHighlights();
14293             fromX = fromY = -1;
14294         }
14295         break;
14296     }
14297 }
14298
14299
14300 void
14301 DropMenuEvent (ChessSquare selection, int x, int y)
14302 {
14303     ChessMove moveType;
14304
14305     switch (gameMode) {
14306       case IcsPlayingWhite:
14307       case MachinePlaysBlack:
14308         if (!WhiteOnMove(currentMove)) {
14309             DisplayMoveError(_("It is Black's turn"));
14310             return;
14311         }
14312         moveType = WhiteDrop;
14313         break;
14314       case IcsPlayingBlack:
14315       case MachinePlaysWhite:
14316         if (WhiteOnMove(currentMove)) {
14317             DisplayMoveError(_("It is White's turn"));
14318             return;
14319         }
14320         moveType = BlackDrop;
14321         break;
14322       case EditGame:
14323         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14324         break;
14325       default:
14326         return;
14327     }
14328
14329     if (moveType == BlackDrop && selection < BlackPawn) {
14330       selection = (ChessSquare) ((int) selection
14331                                  + (int) BlackPawn - (int) WhitePawn);
14332     }
14333     if (boards[currentMove][y][x] != EmptySquare) {
14334         DisplayMoveError(_("That square is occupied"));
14335         return;
14336     }
14337
14338     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14339 }
14340
14341 void
14342 AcceptEvent ()
14343 {
14344     /* Accept a pending offer of any kind from opponent */
14345
14346     if (appData.icsActive) {
14347         SendToICS(ics_prefix);
14348         SendToICS("accept\n");
14349     } else if (cmailMsgLoaded) {
14350         if (currentMove == cmailOldMove &&
14351             commentList[cmailOldMove] != NULL &&
14352             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14353                    "Black offers a draw" : "White offers a draw")) {
14354             TruncateGame();
14355             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14356             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14357         } else {
14358             DisplayError(_("There is no pending offer on this move"), 0);
14359             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14360         }
14361     } else {
14362         /* Not used for offers from chess program */
14363     }
14364 }
14365
14366 void
14367 DeclineEvent ()
14368 {
14369     /* Decline a pending offer of any kind from opponent */
14370
14371     if (appData.icsActive) {
14372         SendToICS(ics_prefix);
14373         SendToICS("decline\n");
14374     } else if (cmailMsgLoaded) {
14375         if (currentMove == cmailOldMove &&
14376             commentList[cmailOldMove] != NULL &&
14377             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14378                    "Black offers a draw" : "White offers a draw")) {
14379 #ifdef NOTDEF
14380             AppendComment(cmailOldMove, "Draw declined", TRUE);
14381             DisplayComment(cmailOldMove - 1, "Draw declined");
14382 #endif /*NOTDEF*/
14383         } else {
14384             DisplayError(_("There is no pending offer on this move"), 0);
14385         }
14386     } else {
14387         /* Not used for offers from chess program */
14388     }
14389 }
14390
14391 void
14392 RematchEvent ()
14393 {
14394     /* Issue ICS rematch command */
14395     if (appData.icsActive) {
14396         SendToICS(ics_prefix);
14397         SendToICS("rematch\n");
14398     }
14399 }
14400
14401 void
14402 CallFlagEvent ()
14403 {
14404     /* Call your opponent's flag (claim a win on time) */
14405     if (appData.icsActive) {
14406         SendToICS(ics_prefix);
14407         SendToICS("flag\n");
14408     } else {
14409         switch (gameMode) {
14410           default:
14411             return;
14412           case MachinePlaysWhite:
14413             if (whiteFlag) {
14414                 if (blackFlag)
14415                   GameEnds(GameIsDrawn, "Both players ran out of time",
14416                            GE_PLAYER);
14417                 else
14418                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14419             } else {
14420                 DisplayError(_("Your opponent is not out of time"), 0);
14421             }
14422             break;
14423           case MachinePlaysBlack:
14424             if (blackFlag) {
14425                 if (whiteFlag)
14426                   GameEnds(GameIsDrawn, "Both players ran out of time",
14427                            GE_PLAYER);
14428                 else
14429                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14430             } else {
14431                 DisplayError(_("Your opponent is not out of time"), 0);
14432             }
14433             break;
14434         }
14435     }
14436 }
14437
14438 void
14439 ClockClick (int which)
14440 {       // [HGM] code moved to back-end from winboard.c
14441         if(which) { // black clock
14442           if (gameMode == EditPosition || gameMode == IcsExamining) {
14443             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14444             SetBlackToPlayEvent();
14445           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14446           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14447           } else if (shiftKey) {
14448             AdjustClock(which, -1);
14449           } else if (gameMode == IcsPlayingWhite ||
14450                      gameMode == MachinePlaysBlack) {
14451             CallFlagEvent();
14452           }
14453         } else { // white clock
14454           if (gameMode == EditPosition || gameMode == IcsExamining) {
14455             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14456             SetWhiteToPlayEvent();
14457           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14458           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14459           } else if (shiftKey) {
14460             AdjustClock(which, -1);
14461           } else if (gameMode == IcsPlayingBlack ||
14462                    gameMode == MachinePlaysWhite) {
14463             CallFlagEvent();
14464           }
14465         }
14466 }
14467
14468 void
14469 DrawEvent ()
14470 {
14471     /* Offer draw or accept pending draw offer from opponent */
14472
14473     if (appData.icsActive) {
14474         /* Note: tournament rules require draw offers to be
14475            made after you make your move but before you punch
14476            your clock.  Currently ICS doesn't let you do that;
14477            instead, you immediately punch your clock after making
14478            a move, but you can offer a draw at any time. */
14479
14480         SendToICS(ics_prefix);
14481         SendToICS("draw\n");
14482         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14483     } else if (cmailMsgLoaded) {
14484         if (currentMove == cmailOldMove &&
14485             commentList[cmailOldMove] != NULL &&
14486             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14487                    "Black offers a draw" : "White offers a draw")) {
14488             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14489             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14490         } else if (currentMove == cmailOldMove + 1) {
14491             char *offer = WhiteOnMove(cmailOldMove) ?
14492               "White offers a draw" : "Black offers a draw";
14493             AppendComment(currentMove, offer, TRUE);
14494             DisplayComment(currentMove - 1, offer);
14495             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14496         } else {
14497             DisplayError(_("You must make your move before offering a draw"), 0);
14498             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14499         }
14500     } else if (first.offeredDraw) {
14501         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14502     } else {
14503         if (first.sendDrawOffers) {
14504             SendToProgram("draw\n", &first);
14505             userOfferedDraw = TRUE;
14506         }
14507     }
14508 }
14509
14510 void
14511 AdjournEvent ()
14512 {
14513     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14514
14515     if (appData.icsActive) {
14516         SendToICS(ics_prefix);
14517         SendToICS("adjourn\n");
14518     } else {
14519         /* Currently GNU Chess doesn't offer or accept Adjourns */
14520     }
14521 }
14522
14523
14524 void
14525 AbortEvent ()
14526 {
14527     /* Offer Abort or accept pending Abort offer from opponent */
14528
14529     if (appData.icsActive) {
14530         SendToICS(ics_prefix);
14531         SendToICS("abort\n");
14532     } else {
14533         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14534     }
14535 }
14536
14537 void
14538 ResignEvent ()
14539 {
14540     /* Resign.  You can do this even if it's not your turn. */
14541
14542     if (appData.icsActive) {
14543         SendToICS(ics_prefix);
14544         SendToICS("resign\n");
14545     } else {
14546         switch (gameMode) {
14547           case MachinePlaysWhite:
14548             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14549             break;
14550           case MachinePlaysBlack:
14551             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14552             break;
14553           case EditGame:
14554             if (cmailMsgLoaded) {
14555                 TruncateGame();
14556                 if (WhiteOnMove(cmailOldMove)) {
14557                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14558                 } else {
14559                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14560                 }
14561                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14562             }
14563             break;
14564           default:
14565             break;
14566         }
14567     }
14568 }
14569
14570
14571 void
14572 StopObservingEvent ()
14573 {
14574     /* Stop observing current games */
14575     SendToICS(ics_prefix);
14576     SendToICS("unobserve\n");
14577 }
14578
14579 void
14580 StopExaminingEvent ()
14581 {
14582     /* Stop observing current game */
14583     SendToICS(ics_prefix);
14584     SendToICS("unexamine\n");
14585 }
14586
14587 void
14588 ForwardInner (int target)
14589 {
14590     int limit; int oldSeekGraphUp = seekGraphUp;
14591
14592     if (appData.debugMode)
14593         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14594                 target, currentMove, forwardMostMove);
14595
14596     if (gameMode == EditPosition)
14597       return;
14598
14599     seekGraphUp = FALSE;
14600     MarkTargetSquares(1);
14601
14602     if (gameMode == PlayFromGameFile && !pausing)
14603       PauseEvent();
14604
14605     if (gameMode == IcsExamining && pausing)
14606       limit = pauseExamForwardMostMove;
14607     else
14608       limit = forwardMostMove;
14609
14610     if (target > limit) target = limit;
14611
14612     if (target > 0 && moveList[target - 1][0]) {
14613         int fromX, fromY, toX, toY;
14614         toX = moveList[target - 1][2] - AAA;
14615         toY = moveList[target - 1][3] - ONE;
14616         if (moveList[target - 1][1] == '@') {
14617             if (appData.highlightLastMove) {
14618                 SetHighlights(-1, -1, toX, toY);
14619             }
14620         } else {
14621             fromX = moveList[target - 1][0] - AAA;
14622             fromY = moveList[target - 1][1] - ONE;
14623             if (target == currentMove + 1) {
14624                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14625             }
14626             if (appData.highlightLastMove) {
14627                 SetHighlights(fromX, fromY, toX, toY);
14628             }
14629         }
14630     }
14631     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14632         gameMode == Training || gameMode == PlayFromGameFile ||
14633         gameMode == AnalyzeFile) {
14634         while (currentMove < target) {
14635             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14636             SendMoveToProgram(currentMove++, &first);
14637         }
14638     } else {
14639         currentMove = target;
14640     }
14641
14642     if (gameMode == EditGame || gameMode == EndOfGame) {
14643         whiteTimeRemaining = timeRemaining[0][currentMove];
14644         blackTimeRemaining = timeRemaining[1][currentMove];
14645     }
14646     DisplayBothClocks();
14647     DisplayMove(currentMove - 1);
14648     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14649     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14650     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14651         DisplayComment(currentMove - 1, commentList[currentMove]);
14652     }
14653     ClearMap(); // [HGM] exclude: invalidate map
14654 }
14655
14656
14657 void
14658 ForwardEvent ()
14659 {
14660     if (gameMode == IcsExamining && !pausing) {
14661         SendToICS(ics_prefix);
14662         SendToICS("forward\n");
14663     } else {
14664         ForwardInner(currentMove + 1);
14665     }
14666 }
14667
14668 void
14669 ToEndEvent ()
14670 {
14671     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14672         /* to optimze, we temporarily turn off analysis mode while we feed
14673          * the remaining moves to the engine. Otherwise we get analysis output
14674          * after each move.
14675          */
14676         if (first.analysisSupport) {
14677           SendToProgram("exit\nforce\n", &first);
14678           first.analyzing = FALSE;
14679         }
14680     }
14681
14682     if (gameMode == IcsExamining && !pausing) {
14683         SendToICS(ics_prefix);
14684         SendToICS("forward 999999\n");
14685     } else {
14686         ForwardInner(forwardMostMove);
14687     }
14688
14689     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14690         /* we have fed all the moves, so reactivate analysis mode */
14691         SendToProgram("analyze\n", &first);
14692         first.analyzing = TRUE;
14693         /*first.maybeThinking = TRUE;*/
14694         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14695     }
14696 }
14697
14698 void
14699 BackwardInner (int target)
14700 {
14701     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14702
14703     if (appData.debugMode)
14704         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14705                 target, currentMove, forwardMostMove);
14706
14707     if (gameMode == EditPosition) return;
14708     seekGraphUp = FALSE;
14709     MarkTargetSquares(1);
14710     if (currentMove <= backwardMostMove) {
14711         ClearHighlights();
14712         DrawPosition(full_redraw, boards[currentMove]);
14713         return;
14714     }
14715     if (gameMode == PlayFromGameFile && !pausing)
14716       PauseEvent();
14717
14718     if (moveList[target][0]) {
14719         int fromX, fromY, toX, toY;
14720         toX = moveList[target][2] - AAA;
14721         toY = moveList[target][3] - ONE;
14722         if (moveList[target][1] == '@') {
14723             if (appData.highlightLastMove) {
14724                 SetHighlights(-1, -1, toX, toY);
14725             }
14726         } else {
14727             fromX = moveList[target][0] - AAA;
14728             fromY = moveList[target][1] - ONE;
14729             if (target == currentMove - 1) {
14730                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14731             }
14732             if (appData.highlightLastMove) {
14733                 SetHighlights(fromX, fromY, toX, toY);
14734             }
14735         }
14736     }
14737     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14738         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14739         while (currentMove > target) {
14740             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14741                 // null move cannot be undone. Reload program with move history before it.
14742                 int i;
14743                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14744                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14745                 }
14746                 SendBoard(&first, i); 
14747               if(second.analyzing) SendBoard(&second, i);
14748                 for(currentMove=i; currentMove<target; currentMove++) {
14749                     SendMoveToProgram(currentMove, &first);
14750                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14751                 }
14752                 break;
14753             }
14754             SendToBoth("undo\n");
14755             currentMove--;
14756         }
14757     } else {
14758         currentMove = target;
14759     }
14760
14761     if (gameMode == EditGame || gameMode == EndOfGame) {
14762         whiteTimeRemaining = timeRemaining[0][currentMove];
14763         blackTimeRemaining = timeRemaining[1][currentMove];
14764     }
14765     DisplayBothClocks();
14766     DisplayMove(currentMove - 1);
14767     DrawPosition(full_redraw, boards[currentMove]);
14768     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14769     // [HGM] PV info: routine tests if comment empty
14770     DisplayComment(currentMove - 1, commentList[currentMove]);
14771     ClearMap(); // [HGM] exclude: invalidate map
14772 }
14773
14774 void
14775 BackwardEvent ()
14776 {
14777     if (gameMode == IcsExamining && !pausing) {
14778         SendToICS(ics_prefix);
14779         SendToICS("backward\n");
14780     } else {
14781         BackwardInner(currentMove - 1);
14782     }
14783 }
14784
14785 void
14786 ToStartEvent ()
14787 {
14788     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14789         /* to optimize, we temporarily turn off analysis mode while we undo
14790          * all the moves. Otherwise we get analysis output after each undo.
14791          */
14792         if (first.analysisSupport) {
14793           SendToProgram("exit\nforce\n", &first);
14794           first.analyzing = FALSE;
14795         }
14796     }
14797
14798     if (gameMode == IcsExamining && !pausing) {
14799         SendToICS(ics_prefix);
14800         SendToICS("backward 999999\n");
14801     } else {
14802         BackwardInner(backwardMostMove);
14803     }
14804
14805     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14806         /* we have fed all the moves, so reactivate analysis mode */
14807         SendToProgram("analyze\n", &first);
14808         first.analyzing = TRUE;
14809         /*first.maybeThinking = TRUE;*/
14810         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14811     }
14812 }
14813
14814 void
14815 ToNrEvent (int to)
14816 {
14817   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14818   if (to >= forwardMostMove) to = forwardMostMove;
14819   if (to <= backwardMostMove) to = backwardMostMove;
14820   if (to < currentMove) {
14821     BackwardInner(to);
14822   } else {
14823     ForwardInner(to);
14824   }
14825 }
14826
14827 void
14828 RevertEvent (Boolean annotate)
14829 {
14830     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14831         return;
14832     }
14833     if (gameMode != IcsExamining) {
14834         DisplayError(_("You are not examining a game"), 0);
14835         return;
14836     }
14837     if (pausing) {
14838         DisplayError(_("You can't revert while pausing"), 0);
14839         return;
14840     }
14841     SendToICS(ics_prefix);
14842     SendToICS("revert\n");
14843 }
14844
14845 void
14846 RetractMoveEvent ()
14847 {
14848     switch (gameMode) {
14849       case MachinePlaysWhite:
14850       case MachinePlaysBlack:
14851         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14852             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14853             return;
14854         }
14855         if (forwardMostMove < 2) return;
14856         currentMove = forwardMostMove = forwardMostMove - 2;
14857         whiteTimeRemaining = timeRemaining[0][currentMove];
14858         blackTimeRemaining = timeRemaining[1][currentMove];
14859         DisplayBothClocks();
14860         DisplayMove(currentMove - 1);
14861         ClearHighlights();/*!! could figure this out*/
14862         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14863         SendToProgram("remove\n", &first);
14864         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14865         break;
14866
14867       case BeginningOfGame:
14868       default:
14869         break;
14870
14871       case IcsPlayingWhite:
14872       case IcsPlayingBlack:
14873         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14874             SendToICS(ics_prefix);
14875             SendToICS("takeback 2\n");
14876         } else {
14877             SendToICS(ics_prefix);
14878             SendToICS("takeback 1\n");
14879         }
14880         break;
14881     }
14882 }
14883
14884 void
14885 MoveNowEvent ()
14886 {
14887     ChessProgramState *cps;
14888
14889     switch (gameMode) {
14890       case MachinePlaysWhite:
14891         if (!WhiteOnMove(forwardMostMove)) {
14892             DisplayError(_("It is your turn"), 0);
14893             return;
14894         }
14895         cps = &first;
14896         break;
14897       case MachinePlaysBlack:
14898         if (WhiteOnMove(forwardMostMove)) {
14899             DisplayError(_("It is your turn"), 0);
14900             return;
14901         }
14902         cps = &first;
14903         break;
14904       case TwoMachinesPlay:
14905         if (WhiteOnMove(forwardMostMove) ==
14906             (first.twoMachinesColor[0] == 'w')) {
14907             cps = &first;
14908         } else {
14909             cps = &second;
14910         }
14911         break;
14912       case BeginningOfGame:
14913       default:
14914         return;
14915     }
14916     SendToProgram("?\n", cps);
14917 }
14918
14919 void
14920 TruncateGameEvent ()
14921 {
14922     EditGameEvent();
14923     if (gameMode != EditGame) return;
14924     TruncateGame();
14925 }
14926
14927 void
14928 TruncateGame ()
14929 {
14930     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14931     if (forwardMostMove > currentMove) {
14932         if (gameInfo.resultDetails != NULL) {
14933             free(gameInfo.resultDetails);
14934             gameInfo.resultDetails = NULL;
14935             gameInfo.result = GameUnfinished;
14936         }
14937         forwardMostMove = currentMove;
14938         HistorySet(parseList, backwardMostMove, forwardMostMove,
14939                    currentMove-1);
14940     }
14941 }
14942
14943 void
14944 HintEvent ()
14945 {
14946     if (appData.noChessProgram) return;
14947     switch (gameMode) {
14948       case MachinePlaysWhite:
14949         if (WhiteOnMove(forwardMostMove)) {
14950             DisplayError(_("Wait until your turn"), 0);
14951             return;
14952         }
14953         break;
14954       case BeginningOfGame:
14955       case MachinePlaysBlack:
14956         if (!WhiteOnMove(forwardMostMove)) {
14957             DisplayError(_("Wait until your turn"), 0);
14958             return;
14959         }
14960         break;
14961       default:
14962         DisplayError(_("No hint available"), 0);
14963         return;
14964     }
14965     SendToProgram("hint\n", &first);
14966     hintRequested = TRUE;
14967 }
14968
14969 void
14970 BookEvent ()
14971 {
14972     if (appData.noChessProgram) return;
14973     switch (gameMode) {
14974       case MachinePlaysWhite:
14975         if (WhiteOnMove(forwardMostMove)) {
14976             DisplayError(_("Wait until your turn"), 0);
14977             return;
14978         }
14979         break;
14980       case BeginningOfGame:
14981       case MachinePlaysBlack:
14982         if (!WhiteOnMove(forwardMostMove)) {
14983             DisplayError(_("Wait until your turn"), 0);
14984             return;
14985         }
14986         break;
14987       case EditPosition:
14988         EditPositionDone(TRUE);
14989         break;
14990       case TwoMachinesPlay:
14991         return;
14992       default:
14993         break;
14994     }
14995     SendToProgram("bk\n", &first);
14996     bookOutput[0] = NULLCHAR;
14997     bookRequested = TRUE;
14998 }
14999
15000 void
15001 AboutGameEvent ()
15002 {
15003     char *tags = PGNTags(&gameInfo);
15004     TagsPopUp(tags, CmailMsg());
15005     free(tags);
15006 }
15007
15008 /* end button procedures */
15009
15010 void
15011 PrintPosition (FILE *fp, int move)
15012 {
15013     int i, j;
15014
15015     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15016         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15017             char c = PieceToChar(boards[move][i][j]);
15018             fputc(c == 'x' ? '.' : c, fp);
15019             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15020         }
15021     }
15022     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15023       fprintf(fp, "white to play\n");
15024     else
15025       fprintf(fp, "black to play\n");
15026 }
15027
15028 void
15029 PrintOpponents (FILE *fp)
15030 {
15031     if (gameInfo.white != NULL) {
15032         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15033     } else {
15034         fprintf(fp, "\n");
15035     }
15036 }
15037
15038 /* Find last component of program's own name, using some heuristics */
15039 void
15040 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15041 {
15042     char *p, *q, c;
15043     int local = (strcmp(host, "localhost") == 0);
15044     while (!local && (p = strchr(prog, ';')) != NULL) {
15045         p++;
15046         while (*p == ' ') p++;
15047         prog = p;
15048     }
15049     if (*prog == '"' || *prog == '\'') {
15050         q = strchr(prog + 1, *prog);
15051     } else {
15052         q = strchr(prog, ' ');
15053     }
15054     if (q == NULL) q = prog + strlen(prog);
15055     p = q;
15056     while (p >= prog && *p != '/' && *p != '\\') p--;
15057     p++;
15058     if(p == prog && *p == '"') p++;
15059     c = *q; *q = 0;
15060     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15061     memcpy(buf, p, q - p);
15062     buf[q - p] = NULLCHAR;
15063     if (!local) {
15064         strcat(buf, "@");
15065         strcat(buf, host);
15066     }
15067 }
15068
15069 char *
15070 TimeControlTagValue ()
15071 {
15072     char buf[MSG_SIZ];
15073     if (!appData.clockMode) {
15074       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15075     } else if (movesPerSession > 0) {
15076       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15077     } else if (timeIncrement == 0) {
15078       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15079     } else {
15080       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15081     }
15082     return StrSave(buf);
15083 }
15084
15085 void
15086 SetGameInfo ()
15087 {
15088     /* This routine is used only for certain modes */
15089     VariantClass v = gameInfo.variant;
15090     ChessMove r = GameUnfinished;
15091     char *p = NULL;
15092
15093     if(keepInfo) return;
15094
15095     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15096         r = gameInfo.result;
15097         p = gameInfo.resultDetails;
15098         gameInfo.resultDetails = NULL;
15099     }
15100     ClearGameInfo(&gameInfo);
15101     gameInfo.variant = v;
15102
15103     switch (gameMode) {
15104       case MachinePlaysWhite:
15105         gameInfo.event = StrSave( appData.pgnEventHeader );
15106         gameInfo.site = StrSave(HostName());
15107         gameInfo.date = PGNDate();
15108         gameInfo.round = StrSave("-");
15109         gameInfo.white = StrSave(first.tidy);
15110         gameInfo.black = StrSave(UserName());
15111         gameInfo.timeControl = TimeControlTagValue();
15112         break;
15113
15114       case MachinePlaysBlack:
15115         gameInfo.event = StrSave( appData.pgnEventHeader );
15116         gameInfo.site = StrSave(HostName());
15117         gameInfo.date = PGNDate();
15118         gameInfo.round = StrSave("-");
15119         gameInfo.white = StrSave(UserName());
15120         gameInfo.black = StrSave(first.tidy);
15121         gameInfo.timeControl = TimeControlTagValue();
15122         break;
15123
15124       case TwoMachinesPlay:
15125         gameInfo.event = StrSave( appData.pgnEventHeader );
15126         gameInfo.site = StrSave(HostName());
15127         gameInfo.date = PGNDate();
15128         if (roundNr > 0) {
15129             char buf[MSG_SIZ];
15130             snprintf(buf, MSG_SIZ, "%d", roundNr);
15131             gameInfo.round = StrSave(buf);
15132         } else {
15133             gameInfo.round = StrSave("-");
15134         }
15135         if (first.twoMachinesColor[0] == 'w') {
15136             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15137             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15138         } else {
15139             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15140             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15141         }
15142         gameInfo.timeControl = TimeControlTagValue();
15143         break;
15144
15145       case EditGame:
15146         gameInfo.event = StrSave("Edited game");
15147         gameInfo.site = StrSave(HostName());
15148         gameInfo.date = PGNDate();
15149         gameInfo.round = StrSave("-");
15150         gameInfo.white = StrSave("-");
15151         gameInfo.black = StrSave("-");
15152         gameInfo.result = r;
15153         gameInfo.resultDetails = p;
15154         break;
15155
15156       case EditPosition:
15157         gameInfo.event = StrSave("Edited position");
15158         gameInfo.site = StrSave(HostName());
15159         gameInfo.date = PGNDate();
15160         gameInfo.round = StrSave("-");
15161         gameInfo.white = StrSave("-");
15162         gameInfo.black = StrSave("-");
15163         break;
15164
15165       case IcsPlayingWhite:
15166       case IcsPlayingBlack:
15167       case IcsObserving:
15168       case IcsExamining:
15169         break;
15170
15171       case PlayFromGameFile:
15172         gameInfo.event = StrSave("Game from non-PGN file");
15173         gameInfo.site = StrSave(HostName());
15174         gameInfo.date = PGNDate();
15175         gameInfo.round = StrSave("-");
15176         gameInfo.white = StrSave("?");
15177         gameInfo.black = StrSave("?");
15178         break;
15179
15180       default:
15181         break;
15182     }
15183 }
15184
15185 void
15186 ReplaceComment (int index, char *text)
15187 {
15188     int len;
15189     char *p;
15190     float score;
15191
15192     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15193        pvInfoList[index-1].depth == len &&
15194        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15195        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15196     while (*text == '\n') text++;
15197     len = strlen(text);
15198     while (len > 0 && text[len - 1] == '\n') len--;
15199
15200     if (commentList[index] != NULL)
15201       free(commentList[index]);
15202
15203     if (len == 0) {
15204         commentList[index] = NULL;
15205         return;
15206     }
15207   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15208       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15209       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15210     commentList[index] = (char *) malloc(len + 2);
15211     strncpy(commentList[index], text, len);
15212     commentList[index][len] = '\n';
15213     commentList[index][len + 1] = NULLCHAR;
15214   } else {
15215     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15216     char *p;
15217     commentList[index] = (char *) malloc(len + 7);
15218     safeStrCpy(commentList[index], "{\n", 3);
15219     safeStrCpy(commentList[index]+2, text, len+1);
15220     commentList[index][len+2] = NULLCHAR;
15221     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15222     strcat(commentList[index], "\n}\n");
15223   }
15224 }
15225
15226 void
15227 CrushCRs (char *text)
15228 {
15229   char *p = text;
15230   char *q = text;
15231   char ch;
15232
15233   do {
15234     ch = *p++;
15235     if (ch == '\r') continue;
15236     *q++ = ch;
15237   } while (ch != '\0');
15238 }
15239
15240 void
15241 AppendComment (int index, char *text, Boolean addBraces)
15242 /* addBraces  tells if we should add {} */
15243 {
15244     int oldlen, len;
15245     char *old;
15246
15247 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15248     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15249
15250     CrushCRs(text);
15251     while (*text == '\n') text++;
15252     len = strlen(text);
15253     while (len > 0 && text[len - 1] == '\n') len--;
15254     text[len] = NULLCHAR;
15255
15256     if (len == 0) return;
15257
15258     if (commentList[index] != NULL) {
15259       Boolean addClosingBrace = addBraces;
15260         old = commentList[index];
15261         oldlen = strlen(old);
15262         while(commentList[index][oldlen-1] ==  '\n')
15263           commentList[index][--oldlen] = NULLCHAR;
15264         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15265         safeStrCpy(commentList[index], old, oldlen + len + 6);
15266         free(old);
15267         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15268         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15269           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15270           while (*text == '\n') { text++; len--; }
15271           commentList[index][--oldlen] = NULLCHAR;
15272       }
15273         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15274         else          strcat(commentList[index], "\n");
15275         strcat(commentList[index], text);
15276         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15277         else          strcat(commentList[index], "\n");
15278     } else {
15279         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15280         if(addBraces)
15281           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15282         else commentList[index][0] = NULLCHAR;
15283         strcat(commentList[index], text);
15284         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15285         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15286     }
15287 }
15288
15289 static char *
15290 FindStr (char * text, char * sub_text)
15291 {
15292     char * result = strstr( text, sub_text );
15293
15294     if( result != NULL ) {
15295         result += strlen( sub_text );
15296     }
15297
15298     return result;
15299 }
15300
15301 /* [AS] Try to extract PV info from PGN comment */
15302 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15303 char *
15304 GetInfoFromComment (int index, char * text)
15305 {
15306     char * sep = text, *p;
15307
15308     if( text != NULL && index > 0 ) {
15309         int score = 0;
15310         int depth = 0;
15311         int time = -1, sec = 0, deci;
15312         char * s_eval = FindStr( text, "[%eval " );
15313         char * s_emt = FindStr( text, "[%emt " );
15314
15315         if( s_eval != NULL || s_emt != NULL ) {
15316             /* New style */
15317             char delim;
15318
15319             if( s_eval != NULL ) {
15320                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15321                     return text;
15322                 }
15323
15324                 if( delim != ']' ) {
15325                     return text;
15326                 }
15327             }
15328
15329             if( s_emt != NULL ) {
15330             }
15331                 return text;
15332         }
15333         else {
15334             /* We expect something like: [+|-]nnn.nn/dd */
15335             int score_lo = 0;
15336
15337             if(*text != '{') return text; // [HGM] braces: must be normal comment
15338
15339             sep = strchr( text, '/' );
15340             if( sep == NULL || sep < (text+4) ) {
15341                 return text;
15342             }
15343
15344             p = text;
15345             if(p[1] == '(') { // comment starts with PV
15346                p = strchr(p, ')'); // locate end of PV
15347                if(p == NULL || sep < p+5) return text;
15348                // at this point we have something like "{(.*) +0.23/6 ..."
15349                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15350                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15351                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15352             }
15353             time = -1; sec = -1; deci = -1;
15354             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15355                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15356                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15357                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15358                 return text;
15359             }
15360
15361             if( score_lo < 0 || score_lo >= 100 ) {
15362                 return text;
15363             }
15364
15365             if(sec >= 0) time = 600*time + 10*sec; else
15366             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15367
15368             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15369
15370             /* [HGM] PV time: now locate end of PV info */
15371             while( *++sep >= '0' && *sep <= '9'); // strip depth
15372             if(time >= 0)
15373             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15374             if(sec >= 0)
15375             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15376             if(deci >= 0)
15377             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15378             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15379         }
15380
15381         if( depth <= 0 ) {
15382             return text;
15383         }
15384
15385         if( time < 0 ) {
15386             time = -1;
15387         }
15388
15389         pvInfoList[index-1].depth = depth;
15390         pvInfoList[index-1].score = score;
15391         pvInfoList[index-1].time  = 10*time; // centi-sec
15392         if(*sep == '}') *sep = 0; else *--sep = '{';
15393         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15394     }
15395     return sep;
15396 }
15397
15398 void
15399 SendToProgram (char *message, ChessProgramState *cps)
15400 {
15401     int count, outCount, error;
15402     char buf[MSG_SIZ];
15403
15404     if (cps->pr == NoProc) return;
15405     Attention(cps);
15406
15407     if (appData.debugMode) {
15408         TimeMark now;
15409         GetTimeMark(&now);
15410         fprintf(debugFP, "%ld >%-6s: %s",
15411                 SubtractTimeMarks(&now, &programStartTime),
15412                 cps->which, message);
15413         if(serverFP)
15414             fprintf(serverFP, "%ld >%-6s: %s",
15415                 SubtractTimeMarks(&now, &programStartTime),
15416                 cps->which, message), fflush(serverFP);
15417     }
15418
15419     count = strlen(message);
15420     outCount = OutputToProcess(cps->pr, message, count, &error);
15421     if (outCount < count && !exiting
15422                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15423       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15424       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15425         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15426             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15427                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15428                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15429                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15430             } else {
15431                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15432                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15433                 gameInfo.result = res;
15434             }
15435             gameInfo.resultDetails = StrSave(buf);
15436         }
15437         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15438         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15439     }
15440 }
15441
15442 void
15443 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15444 {
15445     char *end_str;
15446     char buf[MSG_SIZ];
15447     ChessProgramState *cps = (ChessProgramState *)closure;
15448
15449     if (isr != cps->isr) return; /* Killed intentionally */
15450     if (count <= 0) {
15451         if (count == 0) {
15452             RemoveInputSource(cps->isr);
15453             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15454                     _(cps->which), cps->program);
15455             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15456             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15457                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15458                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15459                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15460                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15461                 } else {
15462                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15463                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15464                     gameInfo.result = res;
15465                 }
15466                 gameInfo.resultDetails = StrSave(buf);
15467             }
15468             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15469             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15470         } else {
15471             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15472                     _(cps->which), cps->program);
15473             RemoveInputSource(cps->isr);
15474
15475             /* [AS] Program is misbehaving badly... kill it */
15476             if( count == -2 ) {
15477                 DestroyChildProcess( cps->pr, 9 );
15478                 cps->pr = NoProc;
15479             }
15480
15481             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15482         }
15483         return;
15484     }
15485
15486     if ((end_str = strchr(message, '\r')) != NULL)
15487       *end_str = NULLCHAR;
15488     if ((end_str = strchr(message, '\n')) != NULL)
15489       *end_str = NULLCHAR;
15490
15491     if (appData.debugMode) {
15492         TimeMark now; int print = 1;
15493         char *quote = ""; char c; int i;
15494
15495         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15496                 char start = message[0];
15497                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15498                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15499                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15500                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15501                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15502                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15503                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15504                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15505                    sscanf(message, "hint: %c", &c)!=1 && 
15506                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15507                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15508                     print = (appData.engineComments >= 2);
15509                 }
15510                 message[0] = start; // restore original message
15511         }
15512         if(print) {
15513                 GetTimeMark(&now);
15514                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15515                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15516                         quote,
15517                         message);
15518                 if(serverFP)
15519                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15520                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15521                         quote,
15522                         message), fflush(serverFP);
15523         }
15524     }
15525
15526     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15527     if (appData.icsEngineAnalyze) {
15528         if (strstr(message, "whisper") != NULL ||
15529              strstr(message, "kibitz") != NULL ||
15530             strstr(message, "tellics") != NULL) return;
15531     }
15532
15533     HandleMachineMove(message, cps);
15534 }
15535
15536
15537 void
15538 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15539 {
15540     char buf[MSG_SIZ];
15541     int seconds;
15542
15543     if( timeControl_2 > 0 ) {
15544         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15545             tc = timeControl_2;
15546         }
15547     }
15548     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15549     inc /= cps->timeOdds;
15550     st  /= cps->timeOdds;
15551
15552     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15553
15554     if (st > 0) {
15555       /* Set exact time per move, normally using st command */
15556       if (cps->stKludge) {
15557         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15558         seconds = st % 60;
15559         if (seconds == 0) {
15560           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15561         } else {
15562           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15563         }
15564       } else {
15565         snprintf(buf, MSG_SIZ, "st %d\n", st);
15566       }
15567     } else {
15568       /* Set conventional or incremental time control, using level command */
15569       if (seconds == 0) {
15570         /* Note old gnuchess bug -- minutes:seconds used to not work.
15571            Fixed in later versions, but still avoid :seconds
15572            when seconds is 0. */
15573         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15574       } else {
15575         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15576                  seconds, inc/1000.);
15577       }
15578     }
15579     SendToProgram(buf, cps);
15580
15581     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15582     /* Orthogonally, limit search to given depth */
15583     if (sd > 0) {
15584       if (cps->sdKludge) {
15585         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15586       } else {
15587         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15588       }
15589       SendToProgram(buf, cps);
15590     }
15591
15592     if(cps->nps >= 0) { /* [HGM] nps */
15593         if(cps->supportsNPS == FALSE)
15594           cps->nps = -1; // don't use if engine explicitly says not supported!
15595         else {
15596           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15597           SendToProgram(buf, cps);
15598         }
15599     }
15600 }
15601
15602 ChessProgramState *
15603 WhitePlayer ()
15604 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15605 {
15606     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15607        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15608         return &second;
15609     return &first;
15610 }
15611
15612 void
15613 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15614 {
15615     char message[MSG_SIZ];
15616     long time, otime;
15617
15618     /* Note: this routine must be called when the clocks are stopped
15619        or when they have *just* been set or switched; otherwise
15620        it will be off by the time since the current tick started.
15621     */
15622     if (machineWhite) {
15623         time = whiteTimeRemaining / 10;
15624         otime = blackTimeRemaining / 10;
15625     } else {
15626         time = blackTimeRemaining / 10;
15627         otime = whiteTimeRemaining / 10;
15628     }
15629     /* [HGM] translate opponent's time by time-odds factor */
15630     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15631
15632     if (time <= 0) time = 1;
15633     if (otime <= 0) otime = 1;
15634
15635     snprintf(message, MSG_SIZ, "time %ld\n", time);
15636     SendToProgram(message, cps);
15637
15638     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15639     SendToProgram(message, cps);
15640 }
15641
15642 int
15643 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15644 {
15645   char buf[MSG_SIZ];
15646   int len = strlen(name);
15647   int val;
15648
15649   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15650     (*p) += len + 1;
15651     sscanf(*p, "%d", &val);
15652     *loc = (val != 0);
15653     while (**p && **p != ' ')
15654       (*p)++;
15655     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15656     SendToProgram(buf, cps);
15657     return TRUE;
15658   }
15659   return FALSE;
15660 }
15661
15662 int
15663 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15664 {
15665   char buf[MSG_SIZ];
15666   int len = strlen(name);
15667   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15668     (*p) += len + 1;
15669     sscanf(*p, "%d", loc);
15670     while (**p && **p != ' ') (*p)++;
15671     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15672     SendToProgram(buf, cps);
15673     return TRUE;
15674   }
15675   return FALSE;
15676 }
15677
15678 int
15679 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15680 {
15681   char buf[MSG_SIZ];
15682   int len = strlen(name);
15683   if (strncmp((*p), name, len) == 0
15684       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15685     (*p) += len + 2;
15686     sscanf(*p, "%[^\"]", loc);
15687     while (**p && **p != '\"') (*p)++;
15688     if (**p == '\"') (*p)++;
15689     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15690     SendToProgram(buf, cps);
15691     return TRUE;
15692   }
15693   return FALSE;
15694 }
15695
15696 int
15697 ParseOption (Option *opt, ChessProgramState *cps)
15698 // [HGM] options: process the string that defines an engine option, and determine
15699 // name, type, default value, and allowed value range
15700 {
15701         char *p, *q, buf[MSG_SIZ];
15702         int n, min = (-1)<<31, max = 1<<31, def;
15703
15704         if(p = strstr(opt->name, " -spin ")) {
15705             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15706             if(max < min) max = min; // enforce consistency
15707             if(def < min) def = min;
15708             if(def > max) def = max;
15709             opt->value = def;
15710             opt->min = min;
15711             opt->max = max;
15712             opt->type = Spin;
15713         } else if((p = strstr(opt->name, " -slider "))) {
15714             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15715             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15716             if(max < min) max = min; // enforce consistency
15717             if(def < min) def = min;
15718             if(def > max) def = max;
15719             opt->value = def;
15720             opt->min = min;
15721             opt->max = max;
15722             opt->type = Spin; // Slider;
15723         } else if((p = strstr(opt->name, " -string "))) {
15724             opt->textValue = p+9;
15725             opt->type = TextBox;
15726         } else if((p = strstr(opt->name, " -file "))) {
15727             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15728             opt->textValue = p+7;
15729             opt->type = FileName; // FileName;
15730         } else if((p = strstr(opt->name, " -path "))) {
15731             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15732             opt->textValue = p+7;
15733             opt->type = PathName; // PathName;
15734         } else if(p = strstr(opt->name, " -check ")) {
15735             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15736             opt->value = (def != 0);
15737             opt->type = CheckBox;
15738         } else if(p = strstr(opt->name, " -combo ")) {
15739             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15740             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15741             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15742             opt->value = n = 0;
15743             while(q = StrStr(q, " /// ")) {
15744                 n++; *q = 0;    // count choices, and null-terminate each of them
15745                 q += 5;
15746                 if(*q == '*') { // remember default, which is marked with * prefix
15747                     q++;
15748                     opt->value = n;
15749                 }
15750                 cps->comboList[cps->comboCnt++] = q;
15751             }
15752             cps->comboList[cps->comboCnt++] = NULL;
15753             opt->max = n + 1;
15754             opt->type = ComboBox;
15755         } else if(p = strstr(opt->name, " -button")) {
15756             opt->type = Button;
15757         } else if(p = strstr(opt->name, " -save")) {
15758             opt->type = SaveButton;
15759         } else return FALSE;
15760         *p = 0; // terminate option name
15761         // now look if the command-line options define a setting for this engine option.
15762         if(cps->optionSettings && cps->optionSettings[0])
15763             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15764         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15765           snprintf(buf, MSG_SIZ, "option %s", p);
15766                 if(p = strstr(buf, ",")) *p = 0;
15767                 if(q = strchr(buf, '=')) switch(opt->type) {
15768                     case ComboBox:
15769                         for(n=0; n<opt->max; n++)
15770                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15771                         break;
15772                     case TextBox:
15773                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15774                         break;
15775                     case Spin:
15776                     case CheckBox:
15777                         opt->value = atoi(q+1);
15778                     default:
15779                         break;
15780                 }
15781                 strcat(buf, "\n");
15782                 SendToProgram(buf, cps);
15783         }
15784         return TRUE;
15785 }
15786
15787 void
15788 FeatureDone (ChessProgramState *cps, int val)
15789 {
15790   DelayedEventCallback cb = GetDelayedEvent();
15791   if ((cb == InitBackEnd3 && cps == &first) ||
15792       (cb == SettingsMenuIfReady && cps == &second) ||
15793       (cb == LoadEngine) ||
15794       (cb == TwoMachinesEventIfReady)) {
15795     CancelDelayedEvent();
15796     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15797   }
15798   cps->initDone = val;
15799 }
15800
15801 /* Parse feature command from engine */
15802 void
15803 ParseFeatures (char *args, ChessProgramState *cps)
15804 {
15805   char *p = args;
15806   char *q;
15807   int val;
15808   char buf[MSG_SIZ];
15809
15810   for (;;) {
15811     while (*p == ' ') p++;
15812     if (*p == NULLCHAR) return;
15813
15814     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15815     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15816     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15817     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15818     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15819     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15820     if (BoolFeature(&p, "reuse", &val, cps)) {
15821       /* Engine can disable reuse, but can't enable it if user said no */
15822       if (!val) cps->reuse = FALSE;
15823       continue;
15824     }
15825     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15826     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15827       if (gameMode == TwoMachinesPlay) {
15828         DisplayTwoMachinesTitle();
15829       } else {
15830         DisplayTitle("");
15831       }
15832       continue;
15833     }
15834     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15835     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15836     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15837     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15838     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15839     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15840     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15841     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15842     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15843     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15844     if (IntFeature(&p, "done", &val, cps)) {
15845       FeatureDone(cps, val);
15846       continue;
15847     }
15848     /* Added by Tord: */
15849     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15850     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15851     /* End of additions by Tord */
15852
15853     /* [HGM] added features: */
15854     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15855     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15856     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15857     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15858     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15859     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15860     if (StringFeature(&p, "option", buf, cps)) {
15861         FREE(cps->option[cps->nrOptions].name);
15862         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15863         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15864         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15865           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15866             SendToProgram(buf, cps);
15867             continue;
15868         }
15869         if(cps->nrOptions >= MAX_OPTIONS) {
15870             cps->nrOptions--;
15871             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15872             DisplayError(buf, 0);
15873         }
15874         continue;
15875     }
15876     /* End of additions by HGM */
15877
15878     /* unknown feature: complain and skip */
15879     q = p;
15880     while (*q && *q != '=') q++;
15881     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15882     SendToProgram(buf, cps);
15883     p = q;
15884     if (*p == '=') {
15885       p++;
15886       if (*p == '\"') {
15887         p++;
15888         while (*p && *p != '\"') p++;
15889         if (*p == '\"') p++;
15890       } else {
15891         while (*p && *p != ' ') p++;
15892       }
15893     }
15894   }
15895
15896 }
15897
15898 void
15899 PeriodicUpdatesEvent (int newState)
15900 {
15901     if (newState == appData.periodicUpdates)
15902       return;
15903
15904     appData.periodicUpdates=newState;
15905
15906     /* Display type changes, so update it now */
15907 //    DisplayAnalysis();
15908
15909     /* Get the ball rolling again... */
15910     if (newState) {
15911         AnalysisPeriodicEvent(1);
15912         StartAnalysisClock();
15913     }
15914 }
15915
15916 void
15917 PonderNextMoveEvent (int newState)
15918 {
15919     if (newState == appData.ponderNextMove) return;
15920     if (gameMode == EditPosition) EditPositionDone(TRUE);
15921     if (newState) {
15922         SendToProgram("hard\n", &first);
15923         if (gameMode == TwoMachinesPlay) {
15924             SendToProgram("hard\n", &second);
15925         }
15926     } else {
15927         SendToProgram("easy\n", &first);
15928         thinkOutput[0] = NULLCHAR;
15929         if (gameMode == TwoMachinesPlay) {
15930             SendToProgram("easy\n", &second);
15931         }
15932     }
15933     appData.ponderNextMove = newState;
15934 }
15935
15936 void
15937 NewSettingEvent (int option, int *feature, char *command, int value)
15938 {
15939     char buf[MSG_SIZ];
15940
15941     if (gameMode == EditPosition) EditPositionDone(TRUE);
15942     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15943     if(feature == NULL || *feature) SendToProgram(buf, &first);
15944     if (gameMode == TwoMachinesPlay) {
15945         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15946     }
15947 }
15948
15949 void
15950 ShowThinkingEvent ()
15951 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15952 {
15953     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15954     int newState = appData.showThinking
15955         // [HGM] thinking: other features now need thinking output as well
15956         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15957
15958     if (oldState == newState) return;
15959     oldState = newState;
15960     if (gameMode == EditPosition) EditPositionDone(TRUE);
15961     if (oldState) {
15962         SendToProgram("post\n", &first);
15963         if (gameMode == TwoMachinesPlay) {
15964             SendToProgram("post\n", &second);
15965         }
15966     } else {
15967         SendToProgram("nopost\n", &first);
15968         thinkOutput[0] = NULLCHAR;
15969         if (gameMode == TwoMachinesPlay) {
15970             SendToProgram("nopost\n", &second);
15971         }
15972     }
15973 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15974 }
15975
15976 void
15977 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15978 {
15979   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15980   if (pr == NoProc) return;
15981   AskQuestion(title, question, replyPrefix, pr);
15982 }
15983
15984 void
15985 TypeInEvent (char firstChar)
15986 {
15987     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15988         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15989         gameMode == AnalyzeMode || gameMode == EditGame || 
15990         gameMode == EditPosition || gameMode == IcsExamining ||
15991         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15992         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15993                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15994                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15995         gameMode == Training) PopUpMoveDialog(firstChar);
15996 }
15997
15998 void
15999 TypeInDoneEvent (char *move)
16000 {
16001         Board board;
16002         int n, fromX, fromY, toX, toY;
16003         char promoChar;
16004         ChessMove moveType;
16005
16006         // [HGM] FENedit
16007         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16008                 EditPositionPasteFEN(move);
16009                 return;
16010         }
16011         // [HGM] movenum: allow move number to be typed in any mode
16012         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16013           ToNrEvent(2*n-1);
16014           return;
16015         }
16016         // undocumented kludge: allow command-line option to be typed in!
16017         // (potentially fatal, and does not implement the effect of the option.)
16018         // should only be used for options that are values on which future decisions will be made,
16019         // and definitely not on options that would be used during initialization.
16020         if(strstr(move, "!!! -") == move) {
16021             ParseArgsFromString(move+4);
16022             return;
16023         }
16024
16025       if (gameMode != EditGame && currentMove != forwardMostMove && 
16026         gameMode != Training) {
16027         DisplayMoveError(_("Displayed move is not current"));
16028       } else {
16029         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16030           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16031         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16032         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16033           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16034           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16035         } else {
16036           DisplayMoveError(_("Could not parse move"));
16037         }
16038       }
16039 }
16040
16041 void
16042 DisplayMove (int moveNumber)
16043 {
16044     char message[MSG_SIZ];
16045     char res[MSG_SIZ];
16046     char cpThinkOutput[MSG_SIZ];
16047
16048     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16049
16050     if (moveNumber == forwardMostMove - 1 ||
16051         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16052
16053         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16054
16055         if (strchr(cpThinkOutput, '\n')) {
16056             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16057         }
16058     } else {
16059         *cpThinkOutput = NULLCHAR;
16060     }
16061
16062     /* [AS] Hide thinking from human user */
16063     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16064         *cpThinkOutput = NULLCHAR;
16065         if( thinkOutput[0] != NULLCHAR ) {
16066             int i;
16067
16068             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16069                 cpThinkOutput[i] = '.';
16070             }
16071             cpThinkOutput[i] = NULLCHAR;
16072             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16073         }
16074     }
16075
16076     if (moveNumber == forwardMostMove - 1 &&
16077         gameInfo.resultDetails != NULL) {
16078         if (gameInfo.resultDetails[0] == NULLCHAR) {
16079           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16080         } else {
16081           snprintf(res, MSG_SIZ, " {%s} %s",
16082                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16083         }
16084     } else {
16085         res[0] = NULLCHAR;
16086     }
16087
16088     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16089         DisplayMessage(res, cpThinkOutput);
16090     } else {
16091       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16092                 WhiteOnMove(moveNumber) ? " " : ".. ",
16093                 parseList[moveNumber], res);
16094         DisplayMessage(message, cpThinkOutput);
16095     }
16096 }
16097
16098 void
16099 DisplayComment (int moveNumber, char *text)
16100 {
16101     char title[MSG_SIZ];
16102
16103     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16104       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16105     } else {
16106       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16107               WhiteOnMove(moveNumber) ? " " : ".. ",
16108               parseList[moveNumber]);
16109     }
16110     if (text != NULL && (appData.autoDisplayComment || commentUp))
16111         CommentPopUp(title, text);
16112 }
16113
16114 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16115  * might be busy thinking or pondering.  It can be omitted if your
16116  * gnuchess is configured to stop thinking immediately on any user
16117  * input.  However, that gnuchess feature depends on the FIONREAD
16118  * ioctl, which does not work properly on some flavors of Unix.
16119  */
16120 void
16121 Attention (ChessProgramState *cps)
16122 {
16123 #if ATTENTION
16124     if (!cps->useSigint) return;
16125     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16126     switch (gameMode) {
16127       case MachinePlaysWhite:
16128       case MachinePlaysBlack:
16129       case TwoMachinesPlay:
16130       case IcsPlayingWhite:
16131       case IcsPlayingBlack:
16132       case AnalyzeMode:
16133       case AnalyzeFile:
16134         /* Skip if we know it isn't thinking */
16135         if (!cps->maybeThinking) return;
16136         if (appData.debugMode)
16137           fprintf(debugFP, "Interrupting %s\n", cps->which);
16138         InterruptChildProcess(cps->pr);
16139         cps->maybeThinking = FALSE;
16140         break;
16141       default:
16142         break;
16143     }
16144 #endif /*ATTENTION*/
16145 }
16146
16147 int
16148 CheckFlags ()
16149 {
16150     if (whiteTimeRemaining <= 0) {
16151         if (!whiteFlag) {
16152             whiteFlag = TRUE;
16153             if (appData.icsActive) {
16154                 if (appData.autoCallFlag &&
16155                     gameMode == IcsPlayingBlack && !blackFlag) {
16156                   SendToICS(ics_prefix);
16157                   SendToICS("flag\n");
16158                 }
16159             } else {
16160                 if (blackFlag) {
16161                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16162                 } else {
16163                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16164                     if (appData.autoCallFlag) {
16165                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16166                         return TRUE;
16167                     }
16168                 }
16169             }
16170         }
16171     }
16172     if (blackTimeRemaining <= 0) {
16173         if (!blackFlag) {
16174             blackFlag = TRUE;
16175             if (appData.icsActive) {
16176                 if (appData.autoCallFlag &&
16177                     gameMode == IcsPlayingWhite && !whiteFlag) {
16178                   SendToICS(ics_prefix);
16179                   SendToICS("flag\n");
16180                 }
16181             } else {
16182                 if (whiteFlag) {
16183                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16184                 } else {
16185                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16186                     if (appData.autoCallFlag) {
16187                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16188                         return TRUE;
16189                     }
16190                 }
16191             }
16192         }
16193     }
16194     return FALSE;
16195 }
16196
16197 void
16198 CheckTimeControl ()
16199 {
16200     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16201         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16202
16203     /*
16204      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16205      */
16206     if ( !WhiteOnMove(forwardMostMove) ) {
16207         /* White made time control */
16208         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16209         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16210         /* [HGM] time odds: correct new time quota for time odds! */
16211                                             / WhitePlayer()->timeOdds;
16212         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16213     } else {
16214         lastBlack -= blackTimeRemaining;
16215         /* Black made time control */
16216         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16217                                             / WhitePlayer()->other->timeOdds;
16218         lastWhite = whiteTimeRemaining;
16219     }
16220 }
16221
16222 void
16223 DisplayBothClocks ()
16224 {
16225     int wom = gameMode == EditPosition ?
16226       !blackPlaysFirst : WhiteOnMove(currentMove);
16227     DisplayWhiteClock(whiteTimeRemaining, wom);
16228     DisplayBlackClock(blackTimeRemaining, !wom);
16229 }
16230
16231
16232 /* Timekeeping seems to be a portability nightmare.  I think everyone
16233    has ftime(), but I'm really not sure, so I'm including some ifdefs
16234    to use other calls if you don't.  Clocks will be less accurate if
16235    you have neither ftime nor gettimeofday.
16236 */
16237
16238 /* VS 2008 requires the #include outside of the function */
16239 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16240 #include <sys/timeb.h>
16241 #endif
16242
16243 /* Get the current time as a TimeMark */
16244 void
16245 GetTimeMark (TimeMark *tm)
16246 {
16247 #if HAVE_GETTIMEOFDAY
16248
16249     struct timeval timeVal;
16250     struct timezone timeZone;
16251
16252     gettimeofday(&timeVal, &timeZone);
16253     tm->sec = (long) timeVal.tv_sec;
16254     tm->ms = (int) (timeVal.tv_usec / 1000L);
16255
16256 #else /*!HAVE_GETTIMEOFDAY*/
16257 #if HAVE_FTIME
16258
16259 // include <sys/timeb.h> / moved to just above start of function
16260     struct timeb timeB;
16261
16262     ftime(&timeB);
16263     tm->sec = (long) timeB.time;
16264     tm->ms = (int) timeB.millitm;
16265
16266 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16267     tm->sec = (long) time(NULL);
16268     tm->ms = 0;
16269 #endif
16270 #endif
16271 }
16272
16273 /* Return the difference in milliseconds between two
16274    time marks.  We assume the difference will fit in a long!
16275 */
16276 long
16277 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16278 {
16279     return 1000L*(tm2->sec - tm1->sec) +
16280            (long) (tm2->ms - tm1->ms);
16281 }
16282
16283
16284 /*
16285  * Code to manage the game clocks.
16286  *
16287  * In tournament play, black starts the clock and then white makes a move.
16288  * We give the human user a slight advantage if he is playing white---the
16289  * clocks don't run until he makes his first move, so it takes zero time.
16290  * Also, we don't account for network lag, so we could get out of sync
16291  * with GNU Chess's clock -- but then, referees are always right.
16292  */
16293
16294 static TimeMark tickStartTM;
16295 static long intendedTickLength;
16296
16297 long
16298 NextTickLength (long timeRemaining)
16299 {
16300     long nominalTickLength, nextTickLength;
16301
16302     if (timeRemaining > 0L && timeRemaining <= 10000L)
16303       nominalTickLength = 100L;
16304     else
16305       nominalTickLength = 1000L;
16306     nextTickLength = timeRemaining % nominalTickLength;
16307     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16308
16309     return nextTickLength;
16310 }
16311
16312 /* Adjust clock one minute up or down */
16313 void
16314 AdjustClock (Boolean which, int dir)
16315 {
16316     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16317     if(which) blackTimeRemaining += 60000*dir;
16318     else      whiteTimeRemaining += 60000*dir;
16319     DisplayBothClocks();
16320     adjustedClock = TRUE;
16321 }
16322
16323 /* Stop clocks and reset to a fresh time control */
16324 void
16325 ResetClocks ()
16326 {
16327     (void) StopClockTimer();
16328     if (appData.icsActive) {
16329         whiteTimeRemaining = blackTimeRemaining = 0;
16330     } else if (searchTime) {
16331         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16332         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16333     } else { /* [HGM] correct new time quote for time odds */
16334         whiteTC = blackTC = fullTimeControlString;
16335         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16336         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16337     }
16338     if (whiteFlag || blackFlag) {
16339         DisplayTitle("");
16340         whiteFlag = blackFlag = FALSE;
16341     }
16342     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16343     DisplayBothClocks();
16344     adjustedClock = FALSE;
16345 }
16346
16347 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16348
16349 /* Decrement running clock by amount of time that has passed */
16350 void
16351 DecrementClocks ()
16352 {
16353     long timeRemaining;
16354     long lastTickLength, fudge;
16355     TimeMark now;
16356
16357     if (!appData.clockMode) return;
16358     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16359
16360     GetTimeMark(&now);
16361
16362     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16363
16364     /* Fudge if we woke up a little too soon */
16365     fudge = intendedTickLength - lastTickLength;
16366     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16367
16368     if (WhiteOnMove(forwardMostMove)) {
16369         if(whiteNPS >= 0) lastTickLength = 0;
16370         timeRemaining = whiteTimeRemaining -= lastTickLength;
16371         if(timeRemaining < 0 && !appData.icsActive) {
16372             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16373             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16374                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16375                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16376             }
16377         }
16378         DisplayWhiteClock(whiteTimeRemaining - fudge,
16379                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16380     } else {
16381         if(blackNPS >= 0) lastTickLength = 0;
16382         timeRemaining = blackTimeRemaining -= lastTickLength;
16383         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16384             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16385             if(suddenDeath) {
16386                 blackStartMove = forwardMostMove;
16387                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16388             }
16389         }
16390         DisplayBlackClock(blackTimeRemaining - fudge,
16391                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16392     }
16393     if (CheckFlags()) return;
16394
16395     if(twoBoards) { // count down secondary board's clocks as well
16396         activePartnerTime -= lastTickLength;
16397         partnerUp = 1;
16398         if(activePartner == 'W')
16399             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16400         else
16401             DisplayBlackClock(activePartnerTime, TRUE);
16402         partnerUp = 0;
16403     }
16404
16405     tickStartTM = now;
16406     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16407     StartClockTimer(intendedTickLength);
16408
16409     /* if the time remaining has fallen below the alarm threshold, sound the
16410      * alarm. if the alarm has sounded and (due to a takeback or time control
16411      * with increment) the time remaining has increased to a level above the
16412      * threshold, reset the alarm so it can sound again.
16413      */
16414
16415     if (appData.icsActive && appData.icsAlarm) {
16416
16417         /* make sure we are dealing with the user's clock */
16418         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16419                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16420            )) return;
16421
16422         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16423             alarmSounded = FALSE;
16424         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16425             PlayAlarmSound();
16426             alarmSounded = TRUE;
16427         }
16428     }
16429 }
16430
16431
16432 /* A player has just moved, so stop the previously running
16433    clock and (if in clock mode) start the other one.
16434    We redisplay both clocks in case we're in ICS mode, because
16435    ICS gives us an update to both clocks after every move.
16436    Note that this routine is called *after* forwardMostMove
16437    is updated, so the last fractional tick must be subtracted
16438    from the color that is *not* on move now.
16439 */
16440 void
16441 SwitchClocks (int newMoveNr)
16442 {
16443     long lastTickLength;
16444     TimeMark now;
16445     int flagged = FALSE;
16446
16447     GetTimeMark(&now);
16448
16449     if (StopClockTimer() && appData.clockMode) {
16450         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16451         if (!WhiteOnMove(forwardMostMove)) {
16452             if(blackNPS >= 0) lastTickLength = 0;
16453             blackTimeRemaining -= lastTickLength;
16454            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16455 //         if(pvInfoList[forwardMostMove].time == -1)
16456                  pvInfoList[forwardMostMove].time =               // use GUI time
16457                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16458         } else {
16459            if(whiteNPS >= 0) lastTickLength = 0;
16460            whiteTimeRemaining -= lastTickLength;
16461            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16462 //         if(pvInfoList[forwardMostMove].time == -1)
16463                  pvInfoList[forwardMostMove].time =
16464                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16465         }
16466         flagged = CheckFlags();
16467     }
16468     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16469     CheckTimeControl();
16470
16471     if (flagged || !appData.clockMode) return;
16472
16473     switch (gameMode) {
16474       case MachinePlaysBlack:
16475       case MachinePlaysWhite:
16476       case BeginningOfGame:
16477         if (pausing) return;
16478         break;
16479
16480       case EditGame:
16481       case PlayFromGameFile:
16482       case IcsExamining:
16483         return;
16484
16485       default:
16486         break;
16487     }
16488
16489     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16490         if(WhiteOnMove(forwardMostMove))
16491              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16492         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16493     }
16494
16495     tickStartTM = now;
16496     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16497       whiteTimeRemaining : blackTimeRemaining);
16498     StartClockTimer(intendedTickLength);
16499 }
16500
16501
16502 /* Stop both clocks */
16503 void
16504 StopClocks ()
16505 {
16506     long lastTickLength;
16507     TimeMark now;
16508
16509     if (!StopClockTimer()) return;
16510     if (!appData.clockMode) return;
16511
16512     GetTimeMark(&now);
16513
16514     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16515     if (WhiteOnMove(forwardMostMove)) {
16516         if(whiteNPS >= 0) lastTickLength = 0;
16517         whiteTimeRemaining -= lastTickLength;
16518         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16519     } else {
16520         if(blackNPS >= 0) lastTickLength = 0;
16521         blackTimeRemaining -= lastTickLength;
16522         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16523     }
16524     CheckFlags();
16525 }
16526
16527 /* Start clock of player on move.  Time may have been reset, so
16528    if clock is already running, stop and restart it. */
16529 void
16530 StartClocks ()
16531 {
16532     (void) StopClockTimer(); /* in case it was running already */
16533     DisplayBothClocks();
16534     if (CheckFlags()) return;
16535
16536     if (!appData.clockMode) return;
16537     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16538
16539     GetTimeMark(&tickStartTM);
16540     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16541       whiteTimeRemaining : blackTimeRemaining);
16542
16543    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16544     whiteNPS = blackNPS = -1;
16545     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16546        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16547         whiteNPS = first.nps;
16548     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16549        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16550         blackNPS = first.nps;
16551     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16552         whiteNPS = second.nps;
16553     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16554         blackNPS = second.nps;
16555     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16556
16557     StartClockTimer(intendedTickLength);
16558 }
16559
16560 char *
16561 TimeString (long ms)
16562 {
16563     long second, minute, hour, day;
16564     char *sign = "";
16565     static char buf[32];
16566
16567     if (ms > 0 && ms <= 9900) {
16568       /* convert milliseconds to tenths, rounding up */
16569       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16570
16571       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16572       return buf;
16573     }
16574
16575     /* convert milliseconds to seconds, rounding up */
16576     /* use floating point to avoid strangeness of integer division
16577        with negative dividends on many machines */
16578     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16579
16580     if (second < 0) {
16581         sign = "-";
16582         second = -second;
16583     }
16584
16585     day = second / (60 * 60 * 24);
16586     second = second % (60 * 60 * 24);
16587     hour = second / (60 * 60);
16588     second = second % (60 * 60);
16589     minute = second / 60;
16590     second = second % 60;
16591
16592     if (day > 0)
16593       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16594               sign, day, hour, minute, second);
16595     else if (hour > 0)
16596       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16597     else
16598       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16599
16600     return buf;
16601 }
16602
16603
16604 /*
16605  * This is necessary because some C libraries aren't ANSI C compliant yet.
16606  */
16607 char *
16608 StrStr (char *string, char *match)
16609 {
16610     int i, length;
16611
16612     length = strlen(match);
16613
16614     for (i = strlen(string) - length; i >= 0; i--, string++)
16615       if (!strncmp(match, string, length))
16616         return string;
16617
16618     return NULL;
16619 }
16620
16621 char *
16622 StrCaseStr (char *string, char *match)
16623 {
16624     int i, j, length;
16625
16626     length = strlen(match);
16627
16628     for (i = strlen(string) - length; i >= 0; i--, string++) {
16629         for (j = 0; j < length; j++) {
16630             if (ToLower(match[j]) != ToLower(string[j]))
16631               break;
16632         }
16633         if (j == length) return string;
16634     }
16635
16636     return NULL;
16637 }
16638
16639 #ifndef _amigados
16640 int
16641 StrCaseCmp (char *s1, char *s2)
16642 {
16643     char c1, c2;
16644
16645     for (;;) {
16646         c1 = ToLower(*s1++);
16647         c2 = ToLower(*s2++);
16648         if (c1 > c2) return 1;
16649         if (c1 < c2) return -1;
16650         if (c1 == NULLCHAR) return 0;
16651     }
16652 }
16653
16654
16655 int
16656 ToLower (int c)
16657 {
16658     return isupper(c) ? tolower(c) : c;
16659 }
16660
16661
16662 int
16663 ToUpper (int c)
16664 {
16665     return islower(c) ? toupper(c) : c;
16666 }
16667 #endif /* !_amigados    */
16668
16669 char *
16670 StrSave (char *s)
16671 {
16672   char *ret;
16673
16674   if ((ret = (char *) malloc(strlen(s) + 1)))
16675     {
16676       safeStrCpy(ret, s, strlen(s)+1);
16677     }
16678   return ret;
16679 }
16680
16681 char *
16682 StrSavePtr (char *s, char **savePtr)
16683 {
16684     if (*savePtr) {
16685         free(*savePtr);
16686     }
16687     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16688       safeStrCpy(*savePtr, s, strlen(s)+1);
16689     }
16690     return(*savePtr);
16691 }
16692
16693 char *
16694 PGNDate ()
16695 {
16696     time_t clock;
16697     struct tm *tm;
16698     char buf[MSG_SIZ];
16699
16700     clock = time((time_t *)NULL);
16701     tm = localtime(&clock);
16702     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16703             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16704     return StrSave(buf);
16705 }
16706
16707
16708 char *
16709 PositionToFEN (int move, char *overrideCastling)
16710 {
16711     int i, j, fromX, fromY, toX, toY;
16712     int whiteToPlay;
16713     char buf[MSG_SIZ];
16714     char *p, *q;
16715     int emptycount;
16716     ChessSquare piece;
16717
16718     whiteToPlay = (gameMode == EditPosition) ?
16719       !blackPlaysFirst : (move % 2 == 0);
16720     p = buf;
16721
16722     /* Piece placement data */
16723     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16724         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16725         emptycount = 0;
16726         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16727             if (boards[move][i][j] == EmptySquare) {
16728                 emptycount++;
16729             } else { ChessSquare piece = boards[move][i][j];
16730                 if (emptycount > 0) {
16731                     if(emptycount<10) /* [HGM] can be >= 10 */
16732                         *p++ = '0' + emptycount;
16733                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16734                     emptycount = 0;
16735                 }
16736                 if(PieceToChar(piece) == '+') {
16737                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16738                     *p++ = '+';
16739                     piece = (ChessSquare)(DEMOTED piece);
16740                 }
16741                 *p++ = PieceToChar(piece);
16742                 if(p[-1] == '~') {
16743                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16744                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16745                     *p++ = '~';
16746                 }
16747             }
16748         }
16749         if (emptycount > 0) {
16750             if(emptycount<10) /* [HGM] can be >= 10 */
16751                 *p++ = '0' + emptycount;
16752             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16753             emptycount = 0;
16754         }
16755         *p++ = '/';
16756     }
16757     *(p - 1) = ' ';
16758
16759     /* [HGM] print Crazyhouse or Shogi holdings */
16760     if( gameInfo.holdingsWidth ) {
16761         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16762         q = p;
16763         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16764             piece = boards[move][i][BOARD_WIDTH-1];
16765             if( piece != EmptySquare )
16766               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16767                   *p++ = PieceToChar(piece);
16768         }
16769         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16770             piece = boards[move][BOARD_HEIGHT-i-1][0];
16771             if( piece != EmptySquare )
16772               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16773                   *p++ = PieceToChar(piece);
16774         }
16775
16776         if( q == p ) *p++ = '-';
16777         *p++ = ']';
16778         *p++ = ' ';
16779     }
16780
16781     /* Active color */
16782     *p++ = whiteToPlay ? 'w' : 'b';
16783     *p++ = ' ';
16784
16785   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16786     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16787   } else {
16788   if(nrCastlingRights) {
16789      q = p;
16790      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16791        /* [HGM] write directly from rights */
16792            if(boards[move][CASTLING][2] != NoRights &&
16793               boards[move][CASTLING][0] != NoRights   )
16794                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16795            if(boards[move][CASTLING][2] != NoRights &&
16796               boards[move][CASTLING][1] != NoRights   )
16797                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16798            if(boards[move][CASTLING][5] != NoRights &&
16799               boards[move][CASTLING][3] != NoRights   )
16800                 *p++ = boards[move][CASTLING][3] + AAA;
16801            if(boards[move][CASTLING][5] != NoRights &&
16802               boards[move][CASTLING][4] != NoRights   )
16803                 *p++ = boards[move][CASTLING][4] + AAA;
16804      } else {
16805
16806         /* [HGM] write true castling rights */
16807         if( nrCastlingRights == 6 ) {
16808             int q, k=0;
16809             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16810                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16811             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16812                  boards[move][CASTLING][2] != NoRights  );
16813             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16814                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16815                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16816                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16817                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16818             }
16819             if(q) *p++ = 'Q';
16820             k = 0;
16821             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16822                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16823             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16824                  boards[move][CASTLING][5] != NoRights  );
16825             if(gameInfo.variant == VariantSChess) {
16826                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16827                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16828                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16829                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16830             }
16831             if(q) *p++ = 'q';
16832         }
16833      }
16834      if (q == p) *p++ = '-'; /* No castling rights */
16835      *p++ = ' ';
16836   }
16837
16838   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16839      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16840     /* En passant target square */
16841     if (move > backwardMostMove) {
16842         fromX = moveList[move - 1][0] - AAA;
16843         fromY = moveList[move - 1][1] - ONE;
16844         toX = moveList[move - 1][2] - AAA;
16845         toY = moveList[move - 1][3] - ONE;
16846         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16847             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16848             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16849             fromX == toX) {
16850             /* 2-square pawn move just happened */
16851             *p++ = toX + AAA;
16852             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16853         } else {
16854             *p++ = '-';
16855         }
16856     } else if(move == backwardMostMove) {
16857         // [HGM] perhaps we should always do it like this, and forget the above?
16858         if((signed char)boards[move][EP_STATUS] >= 0) {
16859             *p++ = boards[move][EP_STATUS] + AAA;
16860             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16861         } else {
16862             *p++ = '-';
16863         }
16864     } else {
16865         *p++ = '-';
16866     }
16867     *p++ = ' ';
16868   }
16869   }
16870
16871     /* [HGM] find reversible plies */
16872     {   int i = 0, j=move;
16873
16874         if (appData.debugMode) { int k;
16875             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16876             for(k=backwardMostMove; k<=forwardMostMove; k++)
16877                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16878
16879         }
16880
16881         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16882         if( j == backwardMostMove ) i += initialRulePlies;
16883         sprintf(p, "%d ", i);
16884         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16885     }
16886     /* Fullmove number */
16887     sprintf(p, "%d", (move / 2) + 1);
16888
16889     return StrSave(buf);
16890 }
16891
16892 Boolean
16893 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16894 {
16895     int i, j;
16896     char *p, c;
16897     int emptycount, virgin[BOARD_FILES];
16898     ChessSquare piece;
16899
16900     p = fen;
16901
16902     /* [HGM] by default clear Crazyhouse holdings, if present */
16903     if(gameInfo.holdingsWidth) {
16904        for(i=0; i<BOARD_HEIGHT; i++) {
16905            board[i][0]             = EmptySquare; /* black holdings */
16906            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16907            board[i][1]             = (ChessSquare) 0; /* black counts */
16908            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16909        }
16910     }
16911
16912     /* Piece placement data */
16913     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16914         j = 0;
16915         for (;;) {
16916             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16917                 if (*p == '/') p++;
16918                 emptycount = gameInfo.boardWidth - j;
16919                 while (emptycount--)
16920                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16921                 break;
16922 #if(BOARD_FILES >= 10)
16923             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16924                 p++; emptycount=10;
16925                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16926                 while (emptycount--)
16927                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16928 #endif
16929             } else if (isdigit(*p)) {
16930                 emptycount = *p++ - '0';
16931                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16932                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16933                 while (emptycount--)
16934                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16935             } else if (*p == '+' || isalpha(*p)) {
16936                 if (j >= gameInfo.boardWidth) return FALSE;
16937                 if(*p=='+') {
16938                     piece = CharToPiece(*++p);
16939                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16940                     piece = (ChessSquare) (PROMOTED piece ); p++;
16941                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16942                 } else piece = CharToPiece(*p++);
16943
16944                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16945                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16946                     piece = (ChessSquare) (PROMOTED piece);
16947                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16948                     p++;
16949                 }
16950                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16951             } else {
16952                 return FALSE;
16953             }
16954         }
16955     }
16956     while (*p == '/' || *p == ' ') p++;
16957
16958     /* [HGM] look for Crazyhouse holdings here */
16959     while(*p==' ') p++;
16960     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16961         if(*p == '[') p++;
16962         if(*p == '-' ) p++; /* empty holdings */ else {
16963             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16964             /* if we would allow FEN reading to set board size, we would   */
16965             /* have to add holdings and shift the board read so far here   */
16966             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16967                 p++;
16968                 if((int) piece >= (int) BlackPawn ) {
16969                     i = (int)piece - (int)BlackPawn;
16970                     i = PieceToNumber((ChessSquare)i);
16971                     if( i >= gameInfo.holdingsSize ) return FALSE;
16972                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16973                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16974                 } else {
16975                     i = (int)piece - (int)WhitePawn;
16976                     i = PieceToNumber((ChessSquare)i);
16977                     if( i >= gameInfo.holdingsSize ) return FALSE;
16978                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16979                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16980                 }
16981             }
16982         }
16983         if(*p == ']') p++;
16984     }
16985
16986     while(*p == ' ') p++;
16987
16988     /* Active color */
16989     c = *p++;
16990     if(appData.colorNickNames) {
16991       if( c == appData.colorNickNames[0] ) c = 'w'; else
16992       if( c == appData.colorNickNames[1] ) c = 'b';
16993     }
16994     switch (c) {
16995       case 'w':
16996         *blackPlaysFirst = FALSE;
16997         break;
16998       case 'b':
16999         *blackPlaysFirst = TRUE;
17000         break;
17001       default:
17002         return FALSE;
17003     }
17004
17005     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17006     /* return the extra info in global variiables             */
17007
17008     /* set defaults in case FEN is incomplete */
17009     board[EP_STATUS] = EP_UNKNOWN;
17010     for(i=0; i<nrCastlingRights; i++ ) {
17011         board[CASTLING][i] =
17012             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17013     }   /* assume possible unless obviously impossible */
17014     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17015     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17016     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17017                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17018     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17019     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17020     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17021                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17022     FENrulePlies = 0;
17023
17024     while(*p==' ') p++;
17025     if(nrCastlingRights) {
17026       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17027       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17028           /* castling indicator present, so default becomes no castlings */
17029           for(i=0; i<nrCastlingRights; i++ ) {
17030                  board[CASTLING][i] = NoRights;
17031           }
17032       }
17033       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17034              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17035              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17036              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17037         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17038
17039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17040             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17041             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17042         }
17043         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17044             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17045         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17046                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17047         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17048                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17049         switch(c) {
17050           case'K':
17051               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17052               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17053               board[CASTLING][2] = whiteKingFile;
17054               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17055               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17056               break;
17057           case'Q':
17058               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17059               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17060               board[CASTLING][2] = whiteKingFile;
17061               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17062               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17063               break;
17064           case'k':
17065               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17066               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17067               board[CASTLING][5] = blackKingFile;
17068               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17069               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17070               break;
17071           case'q':
17072               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17073               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17074               board[CASTLING][5] = blackKingFile;
17075               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17076               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17077           case '-':
17078               break;
17079           default: /* FRC castlings */
17080               if(c >= 'a') { /* black rights */
17081                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17082                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17083                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17084                   if(i == BOARD_RGHT) break;
17085                   board[CASTLING][5] = i;
17086                   c -= AAA;
17087                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17088                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17089                   if(c > i)
17090                       board[CASTLING][3] = c;
17091                   else
17092                       board[CASTLING][4] = c;
17093               } else { /* white rights */
17094                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17095                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17096                     if(board[0][i] == WhiteKing) break;
17097                   if(i == BOARD_RGHT) break;
17098                   board[CASTLING][2] = i;
17099                   c -= AAA - 'a' + 'A';
17100                   if(board[0][c] >= WhiteKing) break;
17101                   if(c > i)
17102                       board[CASTLING][0] = c;
17103                   else
17104                       board[CASTLING][1] = c;
17105               }
17106         }
17107       }
17108       for(i=0; i<nrCastlingRights; i++)
17109         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17110       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17111     if (appData.debugMode) {
17112         fprintf(debugFP, "FEN castling rights:");
17113         for(i=0; i<nrCastlingRights; i++)
17114         fprintf(debugFP, " %d", board[CASTLING][i]);
17115         fprintf(debugFP, "\n");
17116     }
17117
17118       while(*p==' ') p++;
17119     }
17120
17121     /* read e.p. field in games that know e.p. capture */
17122     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17123        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17124       if(*p=='-') {
17125         p++; board[EP_STATUS] = EP_NONE;
17126       } else {
17127          char c = *p++ - AAA;
17128
17129          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17130          if(*p >= '0' && *p <='9') p++;
17131          board[EP_STATUS] = c;
17132       }
17133     }
17134
17135
17136     if(sscanf(p, "%d", &i) == 1) {
17137         FENrulePlies = i; /* 50-move ply counter */
17138         /* (The move number is still ignored)    */
17139     }
17140
17141     return TRUE;
17142 }
17143
17144 void
17145 EditPositionPasteFEN (char *fen)
17146 {
17147   if (fen != NULL) {
17148     Board initial_position;
17149
17150     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17151       DisplayError(_("Bad FEN position in clipboard"), 0);
17152       return ;
17153     } else {
17154       int savedBlackPlaysFirst = blackPlaysFirst;
17155       EditPositionEvent();
17156       blackPlaysFirst = savedBlackPlaysFirst;
17157       CopyBoard(boards[0], initial_position);
17158       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17159       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17160       DisplayBothClocks();
17161       DrawPosition(FALSE, boards[currentMove]);
17162     }
17163   }
17164 }
17165
17166 static char cseq[12] = "\\   ";
17167
17168 Boolean
17169 set_cont_sequence (char *new_seq)
17170 {
17171     int len;
17172     Boolean ret;
17173
17174     // handle bad attempts to set the sequence
17175         if (!new_seq)
17176                 return 0; // acceptable error - no debug
17177
17178     len = strlen(new_seq);
17179     ret = (len > 0) && (len < sizeof(cseq));
17180     if (ret)
17181       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17182     else if (appData.debugMode)
17183       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17184     return ret;
17185 }
17186
17187 /*
17188     reformat a source message so words don't cross the width boundary.  internal
17189     newlines are not removed.  returns the wrapped size (no null character unless
17190     included in source message).  If dest is NULL, only calculate the size required
17191     for the dest buffer.  lp argument indicats line position upon entry, and it's
17192     passed back upon exit.
17193 */
17194 int
17195 wrap (char *dest, char *src, int count, int width, int *lp)
17196 {
17197     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17198
17199     cseq_len = strlen(cseq);
17200     old_line = line = *lp;
17201     ansi = len = clen = 0;
17202
17203     for (i=0; i < count; i++)
17204     {
17205         if (src[i] == '\033')
17206             ansi = 1;
17207
17208         // if we hit the width, back up
17209         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17210         {
17211             // store i & len in case the word is too long
17212             old_i = i, old_len = len;
17213
17214             // find the end of the last word
17215             while (i && src[i] != ' ' && src[i] != '\n')
17216             {
17217                 i--;
17218                 len--;
17219             }
17220
17221             // word too long?  restore i & len before splitting it
17222             if ((old_i-i+clen) >= width)
17223             {
17224                 i = old_i;
17225                 len = old_len;
17226             }
17227
17228             // extra space?
17229             if (i && src[i-1] == ' ')
17230                 len--;
17231
17232             if (src[i] != ' ' && src[i] != '\n')
17233             {
17234                 i--;
17235                 if (len)
17236                     len--;
17237             }
17238
17239             // now append the newline and continuation sequence
17240             if (dest)
17241                 dest[len] = '\n';
17242             len++;
17243             if (dest)
17244                 strncpy(dest+len, cseq, cseq_len);
17245             len += cseq_len;
17246             line = cseq_len;
17247             clen = cseq_len;
17248             continue;
17249         }
17250
17251         if (dest)
17252             dest[len] = src[i];
17253         len++;
17254         if (!ansi)
17255             line++;
17256         if (src[i] == '\n')
17257             line = 0;
17258         if (src[i] == 'm')
17259             ansi = 0;
17260     }
17261     if (dest && appData.debugMode)
17262     {
17263         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17264             count, width, line, len, *lp);
17265         show_bytes(debugFP, src, count);
17266         fprintf(debugFP, "\ndest: ");
17267         show_bytes(debugFP, dest, len);
17268         fprintf(debugFP, "\n");
17269     }
17270     *lp = dest ? line : old_line;
17271
17272     return len;
17273 }
17274
17275 // [HGM] vari: routines for shelving variations
17276 Boolean modeRestore = FALSE;
17277
17278 void
17279 PushInner (int firstMove, int lastMove)
17280 {
17281         int i, j, nrMoves = lastMove - firstMove;
17282
17283         // push current tail of game on stack
17284         savedResult[storedGames] = gameInfo.result;
17285         savedDetails[storedGames] = gameInfo.resultDetails;
17286         gameInfo.resultDetails = NULL;
17287         savedFirst[storedGames] = firstMove;
17288         savedLast [storedGames] = lastMove;
17289         savedFramePtr[storedGames] = framePtr;
17290         framePtr -= nrMoves; // reserve space for the boards
17291         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17292             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17293             for(j=0; j<MOVE_LEN; j++)
17294                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17295             for(j=0; j<2*MOVE_LEN; j++)
17296                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17297             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17298             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17299             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17300             pvInfoList[firstMove+i-1].depth = 0;
17301             commentList[framePtr+i] = commentList[firstMove+i];
17302             commentList[firstMove+i] = NULL;
17303         }
17304
17305         storedGames++;
17306         forwardMostMove = firstMove; // truncate game so we can start variation
17307 }
17308
17309 void
17310 PushTail (int firstMove, int lastMove)
17311 {
17312         if(appData.icsActive) { // only in local mode
17313                 forwardMostMove = currentMove; // mimic old ICS behavior
17314                 return;
17315         }
17316         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17317
17318         PushInner(firstMove, lastMove);
17319         if(storedGames == 1) GreyRevert(FALSE);
17320         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17321 }
17322
17323 void
17324 PopInner (Boolean annotate)
17325 {
17326         int i, j, nrMoves;
17327         char buf[8000], moveBuf[20];
17328
17329         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17330         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17331         nrMoves = savedLast[storedGames] - currentMove;
17332         if(annotate) {
17333                 int cnt = 10;
17334                 if(!WhiteOnMove(currentMove))
17335                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17336                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17337                 for(i=currentMove; i<forwardMostMove; i++) {
17338                         if(WhiteOnMove(i))
17339                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17340                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17341                         strcat(buf, moveBuf);
17342                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17343                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17344                 }
17345                 strcat(buf, ")");
17346         }
17347         for(i=1; i<=nrMoves; i++) { // copy last variation back
17348             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17349             for(j=0; j<MOVE_LEN; j++)
17350                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17351             for(j=0; j<2*MOVE_LEN; j++)
17352                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17353             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17354             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17355             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17356             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17357             commentList[currentMove+i] = commentList[framePtr+i];
17358             commentList[framePtr+i] = NULL;
17359         }
17360         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17361         framePtr = savedFramePtr[storedGames];
17362         gameInfo.result = savedResult[storedGames];
17363         if(gameInfo.resultDetails != NULL) {
17364             free(gameInfo.resultDetails);
17365       }
17366         gameInfo.resultDetails = savedDetails[storedGames];
17367         forwardMostMove = currentMove + nrMoves;
17368 }
17369
17370 Boolean
17371 PopTail (Boolean annotate)
17372 {
17373         if(appData.icsActive) return FALSE; // only in local mode
17374         if(!storedGames) return FALSE; // sanity
17375         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17376
17377         PopInner(annotate);
17378         if(currentMove < forwardMostMove) ForwardEvent(); else
17379         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17380
17381         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17382         return TRUE;
17383 }
17384
17385 void
17386 CleanupTail ()
17387 {       // remove all shelved variations
17388         int i;
17389         for(i=0; i<storedGames; i++) {
17390             if(savedDetails[i])
17391                 free(savedDetails[i]);
17392             savedDetails[i] = NULL;
17393         }
17394         for(i=framePtr; i<MAX_MOVES; i++) {
17395                 if(commentList[i]) free(commentList[i]);
17396                 commentList[i] = NULL;
17397         }
17398         framePtr = MAX_MOVES-1;
17399         storedGames = 0;
17400 }
17401
17402 void
17403 LoadVariation (int index, char *text)
17404 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17405         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17406         int level = 0, move;
17407
17408         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17409         // first find outermost bracketing variation
17410         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17411             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17412                 if(*p == '{') wait = '}'; else
17413                 if(*p == '[') wait = ']'; else
17414                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17415                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17416             }
17417             if(*p == wait) wait = NULLCHAR; // closing ]} found
17418             p++;
17419         }
17420         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17421         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17422         end[1] = NULLCHAR; // clip off comment beyond variation
17423         ToNrEvent(currentMove-1);
17424         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17425         // kludge: use ParsePV() to append variation to game
17426         move = currentMove;
17427         ParsePV(start, TRUE, TRUE);
17428         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17429         ClearPremoveHighlights();
17430         CommentPopDown();
17431         ToNrEvent(currentMove+1);
17432 }
17433