Cure flashing of piece on from-square
[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         DrawSeekOpen();
2517         PlotSeekAd(i);
2518         DrawSeekClose();
2519 }
2520
2521 void
2522 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2523 {
2524         char buf[MSG_SIZ], *ext = "";
2525         VariantClass v = StringToVariant(type);
2526         if(strstr(type, "wild")) {
2527             ext = type + 4; // append wild number
2528             if(v == VariantFischeRandom) type = "chess960"; else
2529             if(v == VariantLoadable) type = "setup"; else
2530             type = VariantName(v);
2531         }
2532         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2533         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2534             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2535             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2536             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2537             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2538             seekNrList[nrOfSeekAds] = nr;
2539             zList[nrOfSeekAds] = 0;
2540             seekAdList[nrOfSeekAds++] = StrSave(buf);
2541             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2542         }
2543 }
2544
2545 void
2546 EraseSeekDot (int i)
2547 {
2548     int x = xList[i], y = yList[i], d=squareSize/4, k;
2549     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2550     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2551     // now replot every dot that overlapped
2552     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2553         int xx = xList[k], yy = yList[k];
2554         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2555             DrawSeekDot(xx, yy, colorList[k]);
2556     }
2557 }
2558
2559 void
2560 RemoveSeekAd (int nr)
2561 {
2562         int i;
2563         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2564             EraseSeekDot(i);
2565             if(seekAdList[i]) free(seekAdList[i]);
2566             seekAdList[i] = seekAdList[--nrOfSeekAds];
2567             seekNrList[i] = seekNrList[nrOfSeekAds];
2568             ratingList[i] = ratingList[nrOfSeekAds];
2569             colorList[i]  = colorList[nrOfSeekAds];
2570             tcList[i] = tcList[nrOfSeekAds];
2571             xList[i]  = xList[nrOfSeekAds];
2572             yList[i]  = yList[nrOfSeekAds];
2573             zList[i]  = zList[nrOfSeekAds];
2574             seekAdList[nrOfSeekAds] = NULL;
2575             break;
2576         }
2577 }
2578
2579 Boolean
2580 MatchSoughtLine (char *line)
2581 {
2582     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2583     int nr, base, inc, u=0; char dummy;
2584
2585     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2586        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2587        (u=1) &&
2588        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2589         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2590         // match: compact and save the line
2591         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2592         return TRUE;
2593     }
2594     return FALSE;
2595 }
2596
2597 int
2598 DrawSeekGraph ()
2599 {
2600     int i;
2601     if(!seekGraphUp) return FALSE;
2602     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2603     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2604
2605     DrawSeekOpen();
2606     DrawSeekBackground(0, 0, w, h);
2607     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2608     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2609     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2610         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2611         yy = h-1-yy;
2612         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2613         if(i%500 == 0) {
2614             char buf[MSG_SIZ];
2615             snprintf(buf, MSG_SIZ, "%d", i);
2616             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2617         }
2618     }
2619     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2620     for(i=1; i<100; i+=(i<10?1:5)) {
2621         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2622         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2623         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2624             char buf[MSG_SIZ];
2625             snprintf(buf, MSG_SIZ, "%d", i);
2626             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2627         }
2628     }
2629     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2630     DrawSeekClose();
2631     return TRUE;
2632 }
2633
2634 int
2635 SeekGraphClick (ClickType click, int x, int y, int moving)
2636 {
2637     static int lastDown = 0, displayed = 0, lastSecond;
2638     if(y < 0) return FALSE;
2639     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2640         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2641         if(!seekGraphUp) return FALSE;
2642         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2643         DrawPosition(TRUE, NULL);
2644         return TRUE;
2645     }
2646     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2647         if(click == Release || moving) return FALSE;
2648         nrOfSeekAds = 0;
2649         soughtPending = TRUE;
2650         SendToICS(ics_prefix);
2651         SendToICS("sought\n"); // should this be "sought all"?
2652     } else { // issue challenge based on clicked ad
2653         int dist = 10000; int i, closest = 0, second = 0;
2654         for(i=0; i<nrOfSeekAds; i++) {
2655             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2656             if(d < dist) { dist = d; closest = i; }
2657             second += (d - zList[i] < 120); // count in-range ads
2658             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2659         }
2660         if(dist < 120) {
2661             char buf[MSG_SIZ];
2662             second = (second > 1);
2663             if(displayed != closest || second != lastSecond) {
2664                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2665                 lastSecond = second; displayed = closest;
2666             }
2667             if(click == Press) {
2668                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2669                 lastDown = closest;
2670                 return TRUE;
2671             } // on press 'hit', only show info
2672             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2673             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2674             SendToICS(ics_prefix);
2675             SendToICS(buf);
2676             return TRUE; // let incoming board of started game pop down the graph
2677         } else if(click == Release) { // release 'miss' is ignored
2678             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2679             if(moving == 2) { // right up-click
2680                 nrOfSeekAds = 0; // refresh graph
2681                 soughtPending = TRUE;
2682                 SendToICS(ics_prefix);
2683                 SendToICS("sought\n"); // should this be "sought all"?
2684             }
2685             return TRUE;
2686         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2687         // press miss or release hit 'pop down' seek graph
2688         seekGraphUp = FALSE;
2689         DrawPosition(TRUE, NULL);
2690     }
2691     return TRUE;
2692 }
2693
2694 void
2695 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2696 {
2697 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2698 #define STARTED_NONE 0
2699 #define STARTED_MOVES 1
2700 #define STARTED_BOARD 2
2701 #define STARTED_OBSERVE 3
2702 #define STARTED_HOLDINGS 4
2703 #define STARTED_CHATTER 5
2704 #define STARTED_COMMENT 6
2705 #define STARTED_MOVES_NOHIDE 7
2706
2707     static int started = STARTED_NONE;
2708     static char parse[20000];
2709     static int parse_pos = 0;
2710     static char buf[BUF_SIZE + 1];
2711     static int firstTime = TRUE, intfSet = FALSE;
2712     static ColorClass prevColor = ColorNormal;
2713     static int savingComment = FALSE;
2714     static int cmatch = 0; // continuation sequence match
2715     char *bp;
2716     char str[MSG_SIZ];
2717     int i, oldi;
2718     int buf_len;
2719     int next_out;
2720     int tkind;
2721     int backup;    /* [DM] For zippy color lines */
2722     char *p;
2723     char talker[MSG_SIZ]; // [HGM] chat
2724     int channel;
2725
2726     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2727
2728     if (appData.debugMode) {
2729       if (!error) {
2730         fprintf(debugFP, "<ICS: ");
2731         show_bytes(debugFP, data, count);
2732         fprintf(debugFP, "\n");
2733       }
2734     }
2735
2736     if (appData.debugMode) { int f = forwardMostMove;
2737         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2738                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2739                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2740     }
2741     if (count > 0) {
2742         /* If last read ended with a partial line that we couldn't parse,
2743            prepend it to the new read and try again. */
2744         if (leftover_len > 0) {
2745             for (i=0; i<leftover_len; i++)
2746               buf[i] = buf[leftover_start + i];
2747         }
2748
2749     /* copy new characters into the buffer */
2750     bp = buf + leftover_len;
2751     buf_len=leftover_len;
2752     for (i=0; i<count; i++)
2753     {
2754         // ignore these
2755         if (data[i] == '\r')
2756             continue;
2757
2758         // join lines split by ICS?
2759         if (!appData.noJoin)
2760         {
2761             /*
2762                 Joining just consists of finding matches against the
2763                 continuation sequence, and discarding that sequence
2764                 if found instead of copying it.  So, until a match
2765                 fails, there's nothing to do since it might be the
2766                 complete sequence, and thus, something we don't want
2767                 copied.
2768             */
2769             if (data[i] == cont_seq[cmatch])
2770             {
2771                 cmatch++;
2772                 if (cmatch == strlen(cont_seq))
2773                 {
2774                     cmatch = 0; // complete match.  just reset the counter
2775
2776                     /*
2777                         it's possible for the ICS to not include the space
2778                         at the end of the last word, making our [correct]
2779                         join operation fuse two separate words.  the server
2780                         does this when the space occurs at the width setting.
2781                     */
2782                     if (!buf_len || buf[buf_len-1] != ' ')
2783                     {
2784                         *bp++ = ' ';
2785                         buf_len++;
2786                     }
2787                 }
2788                 continue;
2789             }
2790             else if (cmatch)
2791             {
2792                 /*
2793                     match failed, so we have to copy what matched before
2794                     falling through and copying this character.  In reality,
2795                     this will only ever be just the newline character, but
2796                     it doesn't hurt to be precise.
2797                 */
2798                 strncpy(bp, cont_seq, cmatch);
2799                 bp += cmatch;
2800                 buf_len += cmatch;
2801                 cmatch = 0;
2802             }
2803         }
2804
2805         // copy this char
2806         *bp++ = data[i];
2807         buf_len++;
2808     }
2809
2810         buf[buf_len] = NULLCHAR;
2811 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2812         next_out = 0;
2813         leftover_start = 0;
2814
2815         i = 0;
2816         while (i < buf_len) {
2817             /* Deal with part of the TELNET option negotiation
2818                protocol.  We refuse to do anything beyond the
2819                defaults, except that we allow the WILL ECHO option,
2820                which ICS uses to turn off password echoing when we are
2821                directly connected to it.  We reject this option
2822                if localLineEditing mode is on (always on in xboard)
2823                and we are talking to port 23, which might be a real
2824                telnet server that will try to keep WILL ECHO on permanently.
2825              */
2826             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2827                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2828                 unsigned char option;
2829                 oldi = i;
2830                 switch ((unsigned char) buf[++i]) {
2831                   case TN_WILL:
2832                     if (appData.debugMode)
2833                       fprintf(debugFP, "\n<WILL ");
2834                     switch (option = (unsigned char) buf[++i]) {
2835                       case TN_ECHO:
2836                         if (appData.debugMode)
2837                           fprintf(debugFP, "ECHO ");
2838                         /* Reply only if this is a change, according
2839                            to the protocol rules. */
2840                         if (remoteEchoOption) break;
2841                         if (appData.localLineEditing &&
2842                             atoi(appData.icsPort) == TN_PORT) {
2843                             TelnetRequest(TN_DONT, TN_ECHO);
2844                         } else {
2845                             EchoOff();
2846                             TelnetRequest(TN_DO, TN_ECHO);
2847                             remoteEchoOption = TRUE;
2848                         }
2849                         break;
2850                       default:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "%d ", option);
2853                         /* Whatever this is, we don't want it. */
2854                         TelnetRequest(TN_DONT, option);
2855                         break;
2856                     }
2857                     break;
2858                   case TN_WONT:
2859                     if (appData.debugMode)
2860                       fprintf(debugFP, "\n<WONT ");
2861                     switch (option = (unsigned char) buf[++i]) {
2862                       case TN_ECHO:
2863                         if (appData.debugMode)
2864                           fprintf(debugFP, "ECHO ");
2865                         /* Reply only if this is a change, according
2866                            to the protocol rules. */
2867                         if (!remoteEchoOption) break;
2868                         EchoOn();
2869                         TelnetRequest(TN_DONT, TN_ECHO);
2870                         remoteEchoOption = FALSE;
2871                         break;
2872                       default:
2873                         if (appData.debugMode)
2874                           fprintf(debugFP, "%d ", (unsigned char) option);
2875                         /* Whatever this is, it must already be turned
2876                            off, because we never agree to turn on
2877                            anything non-default, so according to the
2878                            protocol rules, we don't reply. */
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DO:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DO ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         /* Whatever this is, we refuse to do it. */
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", option);
2890                         TelnetRequest(TN_WONT, option);
2891                         break;
2892                     }
2893                     break;
2894                   case TN_DONT:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<DONT ");
2897                     switch (option = (unsigned char) buf[++i]) {
2898                       default:
2899                         if (appData.debugMode)
2900                           fprintf(debugFP, "%d ", option);
2901                         /* Whatever this is, we are already not doing
2902                            it, because we never agree to do anything
2903                            non-default, so according to the protocol
2904                            rules, we don't reply. */
2905                         break;
2906                     }
2907                     break;
2908                   case TN_IAC:
2909                     if (appData.debugMode)
2910                       fprintf(debugFP, "\n<IAC ");
2911                     /* Doubled IAC; pass it through */
2912                     i--;
2913                     break;
2914                   default:
2915                     if (appData.debugMode)
2916                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2917                     /* Drop all other telnet commands on the floor */
2918                     break;
2919                 }
2920                 if (oldi > next_out)
2921                   SendToPlayer(&buf[next_out], oldi - next_out);
2922                 if (++i > next_out)
2923                   next_out = i;
2924                 continue;
2925             }
2926
2927             /* OK, this at least will *usually* work */
2928             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2929                 loggedOn = TRUE;
2930             }
2931
2932             if (loggedOn && !intfSet) {
2933                 if (ics_type == ICS_ICC) {
2934                   snprintf(str, MSG_SIZ,
2935                           "/set-quietly interface %s\n/set-quietly style 12\n",
2936                           programVersion);
2937                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2938                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2939                 } else if (ics_type == ICS_CHESSNET) {
2940                   snprintf(str, MSG_SIZ, "/style 12\n");
2941                 } else {
2942                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2943                   strcat(str, programVersion);
2944                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2945                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2946                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2947 #ifdef WIN32
2948                   strcat(str, "$iset nohighlight 1\n");
2949 #endif
2950                   strcat(str, "$iset lock 1\n$style 12\n");
2951                 }
2952                 SendToICS(str);
2953                 NotifyFrontendLogin();
2954                 intfSet = TRUE;
2955             }
2956
2957             if (started == STARTED_COMMENT) {
2958                 /* Accumulate characters in comment */
2959                 parse[parse_pos++] = buf[i];
2960                 if (buf[i] == '\n') {
2961                     parse[parse_pos] = NULLCHAR;
2962                     if(chattingPartner>=0) {
2963                         char mess[MSG_SIZ];
2964                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2965                         OutputChatMessage(chattingPartner, mess);
2966                         chattingPartner = -1;
2967                         next_out = i+1; // [HGM] suppress printing in ICS window
2968                     } else
2969                     if(!suppressKibitz) // [HGM] kibitz
2970                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2971                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2972                         int nrDigit = 0, nrAlph = 0, j;
2973                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2974                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2975                         parse[parse_pos] = NULLCHAR;
2976                         // try to be smart: if it does not look like search info, it should go to
2977                         // ICS interaction window after all, not to engine-output window.
2978                         for(j=0; j<parse_pos; j++) { // count letters and digits
2979                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2980                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2981                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2982                         }
2983                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2984                             int depth=0; float score;
2985                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2986                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2987                                 pvInfoList[forwardMostMove-1].depth = depth;
2988                                 pvInfoList[forwardMostMove-1].score = 100*score;
2989                             }
2990                             OutputKibitz(suppressKibitz, parse);
2991                         } else {
2992                             char tmp[MSG_SIZ];
2993                             if(gameMode == IcsObserving) // restore original ICS messages
2994                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2995                             else
2996                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2997                             SendToPlayer(tmp, strlen(tmp));
2998                         }
2999                         next_out = i+1; // [HGM] suppress printing in ICS window
3000                     }
3001                     started = STARTED_NONE;
3002                 } else {
3003                     /* Don't match patterns against characters in comment */
3004                     i++;
3005                     continue;
3006                 }
3007             }
3008             if (started == STARTED_CHATTER) {
3009                 if (buf[i] != '\n') {
3010                     /* Don't match patterns against characters in chatter */
3011                     i++;
3012                     continue;
3013                 }
3014                 started = STARTED_NONE;
3015                 if(suppressKibitz) next_out = i+1;
3016             }
3017
3018             /* Kludge to deal with rcmd protocol */
3019             if (firstTime && looking_at(buf, &i, "\001*")) {
3020                 DisplayFatalError(&buf[1], 0, 1);
3021                 continue;
3022             } else {
3023                 firstTime = FALSE;
3024             }
3025
3026             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3027                 ics_type = ICS_ICC;
3028                 ics_prefix = "/";
3029                 if (appData.debugMode)
3030                   fprintf(debugFP, "ics_type %d\n", ics_type);
3031                 continue;
3032             }
3033             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3034                 ics_type = ICS_FICS;
3035                 ics_prefix = "$";
3036                 if (appData.debugMode)
3037                   fprintf(debugFP, "ics_type %d\n", ics_type);
3038                 continue;
3039             }
3040             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3041                 ics_type = ICS_CHESSNET;
3042                 ics_prefix = "/";
3043                 if (appData.debugMode)
3044                   fprintf(debugFP, "ics_type %d\n", ics_type);
3045                 continue;
3046             }
3047
3048             if (!loggedOn &&
3049                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3050                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3051                  looking_at(buf, &i, "will be \"*\""))) {
3052               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3053               continue;
3054             }
3055
3056             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3057               char buf[MSG_SIZ];
3058               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3059               DisplayIcsInteractionTitle(buf);
3060               have_set_title = TRUE;
3061             }
3062
3063             /* skip finger notes */
3064             if (started == STARTED_NONE &&
3065                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3066                  (buf[i] == '1' && buf[i+1] == '0')) &&
3067                 buf[i+2] == ':' && buf[i+3] == ' ') {
3068               started = STARTED_CHATTER;
3069               i += 3;
3070               continue;
3071             }
3072
3073             oldi = i;
3074             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3075             if(appData.seekGraph) {
3076                 if(soughtPending && MatchSoughtLine(buf+i)) {
3077                     i = strstr(buf+i, "rated") - buf;
3078                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3079                     next_out = leftover_start = i;
3080                     started = STARTED_CHATTER;
3081                     suppressKibitz = TRUE;
3082                     continue;
3083                 }
3084                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3085                         && looking_at(buf, &i, "* ads displayed")) {
3086                     soughtPending = FALSE;
3087                     seekGraphUp = TRUE;
3088                     DrawSeekGraph();
3089                     continue;
3090                 }
3091                 if(appData.autoRefresh) {
3092                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3093                         int s = (ics_type == ICS_ICC); // ICC format differs
3094                         if(seekGraphUp)
3095                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3096                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3099                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = i; // suppress
3101                         continue;
3102                     }
3103                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3104                         char *p = star_match[0];
3105                         while(*p) {
3106                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3107                             while(*p && *p++ != ' '); // next
3108                         }
3109                         looking_at(buf, &i, "*% "); // eat prompt
3110                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3111                         next_out = i;
3112                         continue;
3113                     }
3114                 }
3115             }
3116
3117             /* skip formula vars */
3118             if (started == STARTED_NONE &&
3119                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3120               started = STARTED_CHATTER;
3121               i += 3;
3122               continue;
3123             }
3124
3125             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3126             if (appData.autoKibitz && started == STARTED_NONE &&
3127                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3128                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3129                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3130                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3131                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3132                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3133                         suppressKibitz = TRUE;
3134                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3135                         next_out = i;
3136                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3137                                 && (gameMode == IcsPlayingWhite)) ||
3138                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3139                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3140                             started = STARTED_CHATTER; // own kibitz we simply discard
3141                         else {
3142                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3143                             parse_pos = 0; parse[0] = NULLCHAR;
3144                             savingComment = TRUE;
3145                             suppressKibitz = gameMode != IcsObserving ? 2 :
3146                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3147                         }
3148                         continue;
3149                 } else
3150                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3151                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3152                          && atoi(star_match[0])) {
3153                     // suppress the acknowledgements of our own autoKibitz
3154                     char *p;
3155                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3157                     SendToPlayer(star_match[0], strlen(star_match[0]));
3158                     if(looking_at(buf, &i, "*% ")) // eat prompt
3159                         suppressKibitz = FALSE;
3160                     next_out = i;
3161                     continue;
3162                 }
3163             } // [HGM] kibitz: end of patch
3164
3165             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3166
3167             // [HGM] chat: intercept tells by users for which we have an open chat window
3168             channel = -1;
3169             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3170                                            looking_at(buf, &i, "* whispers:") ||
3171                                            looking_at(buf, &i, "* kibitzes:") ||
3172                                            looking_at(buf, &i, "* shouts:") ||
3173                                            looking_at(buf, &i, "* c-shouts:") ||
3174                                            looking_at(buf, &i, "--> * ") ||
3175                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3176                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3177                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3178                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3179                 int p;
3180                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3181                 chattingPartner = -1;
3182
3183                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3184                 for(p=0; p<MAX_CHAT; p++) {
3185                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3186                     talker[0] = '['; strcat(talker, "] ");
3187                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3188                     chattingPartner = p; break;
3189                     }
3190                 } else
3191                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3192                 for(p=0; p<MAX_CHAT; p++) {
3193                     if(!strcmp("kibitzes", chatPartner[p])) {
3194                         talker[0] = '['; strcat(talker, "] ");
3195                         chattingPartner = p; break;
3196                     }
3197                 } else
3198                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3199                 for(p=0; p<MAX_CHAT; p++) {
3200                     if(!strcmp("whispers", chatPartner[p])) {
3201                         talker[0] = '['; strcat(talker, "] ");
3202                         chattingPartner = p; break;
3203                     }
3204                 } else
3205                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3206                   if(buf[i-8] == '-' && buf[i-3] == 't')
3207                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3208                     if(!strcmp("c-shouts", chatPartner[p])) {
3209                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3210                         chattingPartner = p; break;
3211                     }
3212                   }
3213                   if(chattingPartner < 0)
3214                   for(p=0; p<MAX_CHAT; p++) {
3215                     if(!strcmp("shouts", chatPartner[p])) {
3216                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3217                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3218                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3219                         chattingPartner = p; break;
3220                     }
3221                   }
3222                 }
3223                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3224                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3225                     talker[0] = 0; Colorize(ColorTell, FALSE);
3226                     chattingPartner = p; break;
3227                 }
3228                 if(chattingPartner<0) i = oldi; else {
3229                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3230                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3231                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                     started = STARTED_COMMENT;
3233                     parse_pos = 0; parse[0] = NULLCHAR;
3234                     savingComment = 3 + chattingPartner; // counts as TRUE
3235                     suppressKibitz = TRUE;
3236                     continue;
3237                 }
3238             } // [HGM] chat: end of patch
3239
3240           backup = i;
3241             if (appData.zippyTalk || appData.zippyPlay) {
3242                 /* [DM] Backup address for color zippy lines */
3243 #if ZIPPY
3244                if (loggedOn == TRUE)
3245                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3246                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3247 #endif
3248             } // [DM] 'else { ' deleted
3249                 if (
3250                     /* Regular tells and says */
3251                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3252                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3253                     looking_at(buf, &i, "* says: ") ||
3254                     /* Don't color "message" or "messages" output */
3255                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3256                     looking_at(buf, &i, "*. * at *:*: ") ||
3257                     looking_at(buf, &i, "--* (*:*): ") ||
3258                     /* Message notifications (same color as tells) */
3259                     looking_at(buf, &i, "* has left a message ") ||
3260                     looking_at(buf, &i, "* just sent you a message:\n") ||
3261                     /* Whispers and kibitzes */
3262                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3263                     looking_at(buf, &i, "* kibitzes: ") ||
3264                     /* Channel tells */
3265                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3266
3267                   if (tkind == 1 && strchr(star_match[0], ':')) {
3268                       /* Avoid "tells you:" spoofs in channels */
3269                      tkind = 3;
3270                   }
3271                   if (star_match[0][0] == NULLCHAR ||
3272                       strchr(star_match[0], ' ') ||
3273                       (tkind == 3 && strchr(star_match[1], ' '))) {
3274                     /* Reject bogus matches */
3275                     i = oldi;
3276                   } else {
3277                     if (appData.colorize) {
3278                       if (oldi > next_out) {
3279                         SendToPlayer(&buf[next_out], oldi - next_out);
3280                         next_out = oldi;
3281                       }
3282                       switch (tkind) {
3283                       case 1:
3284                         Colorize(ColorTell, FALSE);
3285                         curColor = ColorTell;
3286                         break;
3287                       case 2:
3288                         Colorize(ColorKibitz, FALSE);
3289                         curColor = ColorKibitz;
3290                         break;
3291                       case 3:
3292                         p = strrchr(star_match[1], '(');
3293                         if (p == NULL) {
3294                           p = star_match[1];
3295                         } else {
3296                           p++;
3297                         }
3298                         if (atoi(p) == 1) {
3299                           Colorize(ColorChannel1, FALSE);
3300                           curColor = ColorChannel1;
3301                         } else {
3302                           Colorize(ColorChannel, FALSE);
3303                           curColor = ColorChannel;
3304                         }
3305                         break;
3306                       case 5:
3307                         curColor = ColorNormal;
3308                         break;
3309                       }
3310                     }
3311                     if (started == STARTED_NONE && appData.autoComment &&
3312                         (gameMode == IcsObserving ||
3313                          gameMode == IcsPlayingWhite ||
3314                          gameMode == IcsPlayingBlack)) {
3315                       parse_pos = i - oldi;
3316                       memcpy(parse, &buf[oldi], parse_pos);
3317                       parse[parse_pos] = NULLCHAR;
3318                       started = STARTED_COMMENT;
3319                       savingComment = TRUE;
3320                     } else {
3321                       started = STARTED_CHATTER;
3322                       savingComment = FALSE;
3323                     }
3324                     loggedOn = TRUE;
3325                     continue;
3326                   }
3327                 }
3328
3329                 if (looking_at(buf, &i, "* s-shouts: ") ||
3330                     looking_at(buf, &i, "* c-shouts: ")) {
3331                     if (appData.colorize) {
3332                         if (oldi > next_out) {
3333                             SendToPlayer(&buf[next_out], oldi - next_out);
3334                             next_out = oldi;
3335                         }
3336                         Colorize(ColorSShout, FALSE);
3337                         curColor = ColorSShout;
3338                     }
3339                     loggedOn = TRUE;
3340                     started = STARTED_CHATTER;
3341                     continue;
3342                 }
3343
3344                 if (looking_at(buf, &i, "--->")) {
3345                     loggedOn = TRUE;
3346                     continue;
3347                 }
3348
3349                 if (looking_at(buf, &i, "* shouts: ") ||
3350                     looking_at(buf, &i, "--> ")) {
3351                     if (appData.colorize) {
3352                         if (oldi > next_out) {
3353                             SendToPlayer(&buf[next_out], oldi - next_out);
3354                             next_out = oldi;
3355                         }
3356                         Colorize(ColorShout, FALSE);
3357                         curColor = ColorShout;
3358                     }
3359                     loggedOn = TRUE;
3360                     started = STARTED_CHATTER;
3361                     continue;
3362                 }
3363
3364                 if (looking_at( buf, &i, "Challenge:")) {
3365                     if (appData.colorize) {
3366                         if (oldi > next_out) {
3367                             SendToPlayer(&buf[next_out], oldi - next_out);
3368                             next_out = oldi;
3369                         }
3370                         Colorize(ColorChallenge, FALSE);
3371                         curColor = ColorChallenge;
3372                     }
3373                     loggedOn = TRUE;
3374                     continue;
3375                 }
3376
3377                 if (looking_at(buf, &i, "* offers you") ||
3378                     looking_at(buf, &i, "* offers to be") ||
3379                     looking_at(buf, &i, "* would like to") ||
3380                     looking_at(buf, &i, "* requests to") ||
3381                     looking_at(buf, &i, "Your opponent offers") ||
3382                     looking_at(buf, &i, "Your opponent requests")) {
3383
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorRequest, FALSE);
3390                         curColor = ColorRequest;
3391                     }
3392                     continue;
3393                 }
3394
3395                 if (looking_at(buf, &i, "* (*) seeking")) {
3396                     if (appData.colorize) {
3397                         if (oldi > next_out) {
3398                             SendToPlayer(&buf[next_out], oldi - next_out);
3399                             next_out = oldi;
3400                         }
3401                         Colorize(ColorSeek, FALSE);
3402                         curColor = ColorSeek;
3403                     }
3404                     continue;
3405             }
3406
3407           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3408
3409             if (looking_at(buf, &i, "\\   ")) {
3410                 if (prevColor != ColorNormal) {
3411                     if (oldi > next_out) {
3412                         SendToPlayer(&buf[next_out], oldi - next_out);
3413                         next_out = oldi;
3414                     }
3415                     Colorize(prevColor, TRUE);
3416                     curColor = prevColor;
3417                 }
3418                 if (savingComment) {
3419                     parse_pos = i - oldi;
3420                     memcpy(parse, &buf[oldi], parse_pos);
3421                     parse[parse_pos] = NULLCHAR;
3422                     started = STARTED_COMMENT;
3423                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3424                         chattingPartner = savingComment - 3; // kludge to remember the box
3425                 } else {
3426                     started = STARTED_CHATTER;
3427                 }
3428                 continue;
3429             }
3430
3431             if (looking_at(buf, &i, "Black Strength :") ||
3432                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3433                 looking_at(buf, &i, "<10>") ||
3434                 looking_at(buf, &i, "#@#")) {
3435                 /* Wrong board style */
3436                 loggedOn = TRUE;
3437                 SendToICS(ics_prefix);
3438                 SendToICS("set style 12\n");
3439                 SendToICS(ics_prefix);
3440                 SendToICS("refresh\n");
3441                 continue;
3442             }
3443
3444             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3445                 ICSInitScript();
3446                 have_sent_ICS_logon = 1;
3447                 continue;
3448             }
3449
3450             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3451                 (looking_at(buf, &i, "\n<12> ") ||
3452                  looking_at(buf, &i, "<12> "))) {
3453                 loggedOn = TRUE;
3454                 if (oldi > next_out) {
3455                     SendToPlayer(&buf[next_out], oldi - next_out);
3456                 }
3457                 next_out = i;
3458                 started = STARTED_BOARD;
3459                 parse_pos = 0;
3460                 continue;
3461             }
3462
3463             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3464                 looking_at(buf, &i, "<b1> ")) {
3465                 if (oldi > next_out) {
3466                     SendToPlayer(&buf[next_out], oldi - next_out);
3467                 }
3468                 next_out = i;
3469                 started = STARTED_HOLDINGS;
3470                 parse_pos = 0;
3471                 continue;
3472             }
3473
3474             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3475                 loggedOn = TRUE;
3476                 /* Header for a move list -- first line */
3477
3478                 switch (ics_getting_history) {
3479                   case H_FALSE:
3480                     switch (gameMode) {
3481                       case IcsIdle:
3482                       case BeginningOfGame:
3483                         /* User typed "moves" or "oldmoves" while we
3484                            were idle.  Pretend we asked for these
3485                            moves and soak them up so user can step
3486                            through them and/or save them.
3487                            */
3488                         Reset(FALSE, TRUE);
3489                         gameMode = IcsObserving;
3490                         ModeHighlight();
3491                         ics_gamenum = -1;
3492                         ics_getting_history = H_GOT_UNREQ_HEADER;
3493                         break;
3494                       case EditGame: /*?*/
3495                       case EditPosition: /*?*/
3496                         /* Should above feature work in these modes too? */
3497                         /* For now it doesn't */
3498                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3499                         break;
3500                       default:
3501                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3502                         break;
3503                     }
3504                     break;
3505                   case H_REQUESTED:
3506                     /* Is this the right one? */
3507                     if (gameInfo.white && gameInfo.black &&
3508                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3509                         strcmp(gameInfo.black, star_match[2]) == 0) {
3510                         /* All is well */
3511                         ics_getting_history = H_GOT_REQ_HEADER;
3512                     }
3513                     break;
3514                   case H_GOT_REQ_HEADER:
3515                   case H_GOT_UNREQ_HEADER:
3516                   case H_GOT_UNWANTED_HEADER:
3517                   case H_GETTING_MOVES:
3518                     /* Should not happen */
3519                     DisplayError(_("Error gathering move list: two headers"), 0);
3520                     ics_getting_history = H_FALSE;
3521                     break;
3522                 }
3523
3524                 /* Save player ratings into gameInfo if needed */
3525                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3526                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3527                     (gameInfo.whiteRating == -1 ||
3528                      gameInfo.blackRating == -1)) {
3529
3530                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3531                     gameInfo.blackRating = string_to_rating(star_match[3]);
3532                     if (appData.debugMode)
3533                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3534                               gameInfo.whiteRating, gameInfo.blackRating);
3535                 }
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i,
3540               "* * match, initial time: * minute*, increment: * second")) {
3541                 /* Header for a move list -- second line */
3542                 /* Initial board will follow if this is a wild game */
3543                 if (gameInfo.event != NULL) free(gameInfo.event);
3544                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3545                 gameInfo.event = StrSave(str);
3546                 /* [HGM] we switched variant. Translate boards if needed. */
3547                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "Move  ")) {
3552                 /* Beginning of a move list */
3553                 switch (ics_getting_history) {
3554                   case H_FALSE:
3555                     /* Normally should not happen */
3556                     /* Maybe user hit reset while we were parsing */
3557                     break;
3558                   case H_REQUESTED:
3559                     /* Happens if we are ignoring a move list that is not
3560                      * the one we just requested.  Common if the user
3561                      * tries to observe two games without turning off
3562                      * getMoveList */
3563                     break;
3564                   case H_GETTING_MOVES:
3565                     /* Should not happen */
3566                     DisplayError(_("Error gathering move list: nested"), 0);
3567                     ics_getting_history = H_FALSE;
3568                     break;
3569                   case H_GOT_REQ_HEADER:
3570                     ics_getting_history = H_GETTING_MOVES;
3571                     started = STARTED_MOVES;
3572                     parse_pos = 0;
3573                     if (oldi > next_out) {
3574                         SendToPlayer(&buf[next_out], oldi - next_out);
3575                     }
3576                     break;
3577                   case H_GOT_UNREQ_HEADER:
3578                     ics_getting_history = H_GETTING_MOVES;
3579                     started = STARTED_MOVES_NOHIDE;
3580                     parse_pos = 0;
3581                     break;
3582                   case H_GOT_UNWANTED_HEADER:
3583                     ics_getting_history = H_FALSE;
3584                     break;
3585                 }
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "% ") ||
3590                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3591                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3592                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3593                     soughtPending = FALSE;
3594                     seekGraphUp = TRUE;
3595                     DrawSeekGraph();
3596                 }
3597                 if(suppressKibitz) next_out = i;
3598                 savingComment = FALSE;
3599                 suppressKibitz = 0;
3600                 switch (started) {
3601                   case STARTED_MOVES:
3602                   case STARTED_MOVES_NOHIDE:
3603                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3604                     parse[parse_pos + i - oldi] = NULLCHAR;
3605                     ParseGameHistory(parse);
3606 #if ZIPPY
3607                     if (appData.zippyPlay && first.initDone) {
3608                         FeedMovesToProgram(&first, forwardMostMove);
3609                         if (gameMode == IcsPlayingWhite) {
3610                             if (WhiteOnMove(forwardMostMove)) {
3611                                 if (first.sendTime) {
3612                                   if (first.useColors) {
3613                                     SendToProgram("black\n", &first);
3614                                   }
3615                                   SendTimeRemaining(&first, TRUE);
3616                                 }
3617                                 if (first.useColors) {
3618                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3619                                 }
3620                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3621                                 first.maybeThinking = TRUE;
3622                             } else {
3623                                 if (first.usePlayother) {
3624                                   if (first.sendTime) {
3625                                     SendTimeRemaining(&first, TRUE);
3626                                   }
3627                                   SendToProgram("playother\n", &first);
3628                                   firstMove = FALSE;
3629                                 } else {
3630                                   firstMove = TRUE;
3631                                 }
3632                             }
3633                         } else if (gameMode == IcsPlayingBlack) {
3634                             if (!WhiteOnMove(forwardMostMove)) {
3635                                 if (first.sendTime) {
3636                                   if (first.useColors) {
3637                                     SendToProgram("white\n", &first);
3638                                   }
3639                                   SendTimeRemaining(&first, FALSE);
3640                                 }
3641                                 if (first.useColors) {
3642                                   SendToProgram("black\n", &first);
3643                                 }
3644                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3645                                 first.maybeThinking = TRUE;
3646                             } else {
3647                                 if (first.usePlayother) {
3648                                   if (first.sendTime) {
3649                                     SendTimeRemaining(&first, FALSE);
3650                                   }
3651                                   SendToProgram("playother\n", &first);
3652                                   firstMove = FALSE;
3653                                 } else {
3654                                   firstMove = TRUE;
3655                                 }
3656                             }
3657                         }
3658                     }
3659 #endif
3660                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3661                         /* Moves came from oldmoves or moves command
3662                            while we weren't doing anything else.
3663                            */
3664                         currentMove = forwardMostMove;
3665                         ClearHighlights();/*!!could figure this out*/
3666                         flipView = appData.flipView;
3667                         DrawPosition(TRUE, boards[currentMove]);
3668                         DisplayBothClocks();
3669                         snprintf(str, MSG_SIZ, "%s %s %s",
3670                                 gameInfo.white, _("vs."),  gameInfo.black);
3671                         DisplayTitle(str);
3672                         gameMode = IcsIdle;
3673                     } else {
3674                         /* Moves were history of an active game */
3675                         if (gameInfo.resultDetails != NULL) {
3676                             free(gameInfo.resultDetails);
3677                             gameInfo.resultDetails = NULL;
3678                         }
3679                     }
3680                     HistorySet(parseList, backwardMostMove,
3681                                forwardMostMove, currentMove-1);
3682                     DisplayMove(currentMove - 1);
3683                     if (started == STARTED_MOVES) next_out = i;
3684                     started = STARTED_NONE;
3685                     ics_getting_history = H_FALSE;
3686                     break;
3687
3688                   case STARTED_OBSERVE:
3689                     started = STARTED_NONE;
3690                     SendToICS(ics_prefix);
3691                     SendToICS("refresh\n");
3692                     break;
3693
3694                   default:
3695                     break;
3696                 }
3697                 if(bookHit) { // [HGM] book: simulate book reply
3698                     static char bookMove[MSG_SIZ]; // a bit generous?
3699
3700                     programStats.nodes = programStats.depth = programStats.time =
3701                     programStats.score = programStats.got_only_move = 0;
3702                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3703
3704                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3705                     strcat(bookMove, bookHit);
3706                     HandleMachineMove(bookMove, &first);
3707                 }
3708                 continue;
3709             }
3710
3711             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3712                  started == STARTED_HOLDINGS ||
3713                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3714                 /* Accumulate characters in move list or board */
3715                 parse[parse_pos++] = buf[i];
3716             }
3717
3718             /* Start of game messages.  Mostly we detect start of game
3719                when the first board image arrives.  On some versions
3720                of the ICS, though, we need to do a "refresh" after starting
3721                to observe in order to get the current board right away. */
3722             if (looking_at(buf, &i, "Adding game * to observation list")) {
3723                 started = STARTED_OBSERVE;
3724                 continue;
3725             }
3726
3727             /* Handle auto-observe */
3728             if (appData.autoObserve &&
3729                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3730                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3731                 char *player;
3732                 /* Choose the player that was highlighted, if any. */
3733                 if (star_match[0][0] == '\033' ||
3734                     star_match[1][0] != '\033') {
3735                     player = star_match[0];
3736                 } else {
3737                     player = star_match[2];
3738                 }
3739                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3740                         ics_prefix, StripHighlightAndTitle(player));
3741                 SendToICS(str);
3742
3743                 /* Save ratings from notify string */
3744                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3745                 player1Rating = string_to_rating(star_match[1]);
3746                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3747                 player2Rating = string_to_rating(star_match[3]);
3748
3749                 if (appData.debugMode)
3750                   fprintf(debugFP,
3751                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3752                           player1Name, player1Rating,
3753                           player2Name, player2Rating);
3754
3755                 continue;
3756             }
3757
3758             /* Deal with automatic examine mode after a game,
3759                and with IcsObserving -> IcsExamining transition */
3760             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3761                 looking_at(buf, &i, "has made you an examiner of game *")) {
3762
3763                 int gamenum = atoi(star_match[0]);
3764                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3765                     gamenum == ics_gamenum) {
3766                     /* We were already playing or observing this game;
3767                        no need to refetch history */
3768                     gameMode = IcsExamining;
3769                     if (pausing) {
3770                         pauseExamForwardMostMove = forwardMostMove;
3771                     } else if (currentMove < forwardMostMove) {
3772                         ForwardInner(forwardMostMove);
3773                     }
3774                 } else {
3775                     /* I don't think this case really can happen */
3776                     SendToICS(ics_prefix);
3777                     SendToICS("refresh\n");
3778                 }
3779                 continue;
3780             }
3781
3782             /* Error messages */
3783 //          if (ics_user_moved) {
3784             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3785                 if (looking_at(buf, &i, "Illegal move") ||
3786                     looking_at(buf, &i, "Not a legal move") ||
3787                     looking_at(buf, &i, "Your king is in check") ||
3788                     looking_at(buf, &i, "It isn't your turn") ||
3789                     looking_at(buf, &i, "It is not your move")) {
3790                     /* Illegal move */
3791                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3792                         currentMove = forwardMostMove-1;
3793                         DisplayMove(currentMove - 1); /* before DMError */
3794                         DrawPosition(FALSE, boards[currentMove]);
3795                         SwitchClocks(forwardMostMove-1); // [HGM] race
3796                         DisplayBothClocks();
3797                     }
3798                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3799                     ics_user_moved = 0;
3800                     continue;
3801                 }
3802             }
3803
3804             if (looking_at(buf, &i, "still have time") ||
3805                 looking_at(buf, &i, "not out of time") ||
3806                 looking_at(buf, &i, "either player is out of time") ||
3807                 looking_at(buf, &i, "has timeseal; checking")) {
3808                 /* We must have called his flag a little too soon */
3809                 whiteFlag = blackFlag = FALSE;
3810                 continue;
3811             }
3812
3813             if (looking_at(buf, &i, "added * seconds to") ||
3814                 looking_at(buf, &i, "seconds were added to")) {
3815                 /* Update the clocks */
3816                 SendToICS(ics_prefix);
3817                 SendToICS("refresh\n");
3818                 continue;
3819             }
3820
3821             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3822                 ics_clock_paused = TRUE;
3823                 StopClocks();
3824                 continue;
3825             }
3826
3827             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3828                 ics_clock_paused = FALSE;
3829                 StartClocks();
3830                 continue;
3831             }
3832
3833             /* Grab player ratings from the Creating: message.
3834                Note we have to check for the special case when
3835                the ICS inserts things like [white] or [black]. */
3836             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3837                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3838                 /* star_matches:
3839                    0    player 1 name (not necessarily white)
3840                    1    player 1 rating
3841                    2    empty, white, or black (IGNORED)
3842                    3    player 2 name (not necessarily black)
3843                    4    player 2 rating
3844
3845                    The names/ratings are sorted out when the game
3846                    actually starts (below).
3847                 */
3848                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3849                 player1Rating = string_to_rating(star_match[1]);
3850                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3851                 player2Rating = string_to_rating(star_match[4]);
3852
3853                 if (appData.debugMode)
3854                   fprintf(debugFP,
3855                           "Ratings from 'Creating:' %s %d, %s %d\n",
3856                           player1Name, player1Rating,
3857                           player2Name, player2Rating);
3858
3859                 continue;
3860             }
3861
3862             /* Improved generic start/end-of-game messages */
3863             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3864                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3865                 /* If tkind == 0: */
3866                 /* star_match[0] is the game number */
3867                 /*           [1] is the white player's name */
3868                 /*           [2] is the black player's name */
3869                 /* For end-of-game: */
3870                 /*           [3] is the reason for the game end */
3871                 /*           [4] is a PGN end game-token, preceded by " " */
3872                 /* For start-of-game: */
3873                 /*           [3] begins with "Creating" or "Continuing" */
3874                 /*           [4] is " *" or empty (don't care). */
3875                 int gamenum = atoi(star_match[0]);
3876                 char *whitename, *blackname, *why, *endtoken;
3877                 ChessMove endtype = EndOfFile;
3878
3879                 if (tkind == 0) {
3880                   whitename = star_match[1];
3881                   blackname = star_match[2];
3882                   why = star_match[3];
3883                   endtoken = star_match[4];
3884                 } else {
3885                   whitename = star_match[1];
3886                   blackname = star_match[3];
3887                   why = star_match[5];
3888                   endtoken = star_match[6];
3889                 }
3890
3891                 /* Game start messages */
3892                 if (strncmp(why, "Creating ", 9) == 0 ||
3893                     strncmp(why, "Continuing ", 11) == 0) {
3894                     gs_gamenum = gamenum;
3895                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3896                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3897                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3898 #if ZIPPY
3899                     if (appData.zippyPlay) {
3900                         ZippyGameStart(whitename, blackname);
3901                     }
3902 #endif /*ZIPPY*/
3903                     partnerBoardValid = FALSE; // [HGM] bughouse
3904                     continue;
3905                 }
3906
3907                 /* Game end messages */
3908                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3909                     ics_gamenum != gamenum) {
3910                     continue;
3911                 }
3912                 while (endtoken[0] == ' ') endtoken++;
3913                 switch (endtoken[0]) {
3914                   case '*':
3915                   default:
3916                     endtype = GameUnfinished;
3917                     break;
3918                   case '0':
3919                     endtype = BlackWins;
3920                     break;
3921                   case '1':
3922                     if (endtoken[1] == '/')
3923                       endtype = GameIsDrawn;
3924                     else
3925                       endtype = WhiteWins;
3926                     break;
3927                 }
3928                 GameEnds(endtype, why, GE_ICS);
3929 #if ZIPPY
3930                 if (appData.zippyPlay && first.initDone) {
3931                     ZippyGameEnd(endtype, why);
3932                     if (first.pr == NoProc) {
3933                       /* Start the next process early so that we'll
3934                          be ready for the next challenge */
3935                       StartChessProgram(&first);
3936                     }
3937                     /* Send "new" early, in case this command takes
3938                        a long time to finish, so that we'll be ready
3939                        for the next challenge. */
3940                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3941                     Reset(TRUE, TRUE);
3942                 }
3943 #endif /*ZIPPY*/
3944                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3945                 continue;
3946             }
3947
3948             if (looking_at(buf, &i, "Removing game * from observation") ||
3949                 looking_at(buf, &i, "no longer observing game *") ||
3950                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3951                 if (gameMode == IcsObserving &&
3952                     atoi(star_match[0]) == ics_gamenum)
3953                   {
3954                       /* icsEngineAnalyze */
3955                       if (appData.icsEngineAnalyze) {
3956                             ExitAnalyzeMode();
3957                             ModeHighlight();
3958                       }
3959                       StopClocks();
3960                       gameMode = IcsIdle;
3961                       ics_gamenum = -1;
3962                       ics_user_moved = FALSE;
3963                   }
3964                 continue;
3965             }
3966
3967             if (looking_at(buf, &i, "no longer examining game *")) {
3968                 if (gameMode == IcsExamining &&
3969                     atoi(star_match[0]) == ics_gamenum)
3970                   {
3971                       gameMode = IcsIdle;
3972                       ics_gamenum = -1;
3973                       ics_user_moved = FALSE;
3974                   }
3975                 continue;
3976             }
3977
3978             /* Advance leftover_start past any newlines we find,
3979                so only partial lines can get reparsed */
3980             if (looking_at(buf, &i, "\n")) {
3981                 prevColor = curColor;
3982                 if (curColor != ColorNormal) {
3983                     if (oldi > next_out) {
3984                         SendToPlayer(&buf[next_out], oldi - next_out);
3985                         next_out = oldi;
3986                     }
3987                     Colorize(ColorNormal, FALSE);
3988                     curColor = ColorNormal;
3989                 }
3990                 if (started == STARTED_BOARD) {
3991                     started = STARTED_NONE;
3992                     parse[parse_pos] = NULLCHAR;
3993                     ParseBoard12(parse);
3994                     ics_user_moved = 0;
3995
3996                     /* Send premove here */
3997                     if (appData.premove) {
3998                       char str[MSG_SIZ];
3999                       if (currentMove == 0 &&
4000                           gameMode == IcsPlayingWhite &&
4001                           appData.premoveWhite) {
4002                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4003                         if (appData.debugMode)
4004                           fprintf(debugFP, "Sending premove:\n");
4005                         SendToICS(str);
4006                       } else if (currentMove == 1 &&
4007                                  gameMode == IcsPlayingBlack &&
4008                                  appData.premoveBlack) {
4009                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4010                         if (appData.debugMode)
4011                           fprintf(debugFP, "Sending premove:\n");
4012                         SendToICS(str);
4013                       } else if (gotPremove) {
4014                         gotPremove = 0;
4015                         ClearPremoveHighlights();
4016                         if (appData.debugMode)
4017                           fprintf(debugFP, "Sending premove:\n");
4018                           UserMoveEvent(premoveFromX, premoveFromY,
4019                                         premoveToX, premoveToY,
4020                                         premovePromoChar);
4021                       }
4022                     }
4023
4024                     /* Usually suppress following prompt */
4025                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4026                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4027                         if (looking_at(buf, &i, "*% ")) {
4028                             savingComment = FALSE;
4029                             suppressKibitz = 0;
4030                         }
4031                     }
4032                     next_out = i;
4033                 } else if (started == STARTED_HOLDINGS) {
4034                     int gamenum;
4035                     char new_piece[MSG_SIZ];
4036                     started = STARTED_NONE;
4037                     parse[parse_pos] = NULLCHAR;
4038                     if (appData.debugMode)
4039                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4040                                                         parse, currentMove);
4041                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4042                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4043                         if (gameInfo.variant == VariantNormal) {
4044                           /* [HGM] We seem to switch variant during a game!
4045                            * Presumably no holdings were displayed, so we have
4046                            * to move the position two files to the right to
4047                            * create room for them!
4048                            */
4049                           VariantClass newVariant;
4050                           switch(gameInfo.boardWidth) { // base guess on board width
4051                                 case 9:  newVariant = VariantShogi; break;
4052                                 case 10: newVariant = VariantGreat; break;
4053                                 default: newVariant = VariantCrazyhouse; break;
4054                           }
4055                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4056                           /* Get a move list just to see the header, which
4057                              will tell us whether this is really bug or zh */
4058                           if (ics_getting_history == H_FALSE) {
4059                             ics_getting_history = H_REQUESTED;
4060                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4061                             SendToICS(str);
4062                           }
4063                         }
4064                         new_piece[0] = NULLCHAR;
4065                         sscanf(parse, "game %d white [%s black [%s <- %s",
4066                                &gamenum, white_holding, black_holding,
4067                                new_piece);
4068                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4069                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4070                         /* [HGM] copy holdings to board holdings area */
4071                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4072                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4073                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4074 #if ZIPPY
4075                         if (appData.zippyPlay && first.initDone) {
4076                             ZippyHoldings(white_holding, black_holding,
4077                                           new_piece);
4078                         }
4079 #endif /*ZIPPY*/
4080                         if (tinyLayout || smallLayout) {
4081                             char wh[16], bh[16];
4082                             PackHolding(wh, white_holding);
4083                             PackHolding(bh, black_holding);
4084                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4085                                     gameInfo.white, gameInfo.black);
4086                         } else {
4087                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4088                                     gameInfo.white, white_holding, _("vs."),
4089                                     gameInfo.black, black_holding);
4090                         }
4091                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4092                         DrawPosition(FALSE, boards[currentMove]);
4093                         DisplayTitle(str);
4094                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4095                         sscanf(parse, "game %d white [%s black [%s <- %s",
4096                                &gamenum, white_holding, black_holding,
4097                                new_piece);
4098                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4099                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4100                         /* [HGM] copy holdings to partner-board holdings area */
4101                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4102                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4103                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4104                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4105                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4106                       }
4107                     }
4108                     /* Suppress following prompt */
4109                     if (looking_at(buf, &i, "*% ")) {
4110                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4111                         savingComment = FALSE;
4112                         suppressKibitz = 0;
4113                     }
4114                     next_out = i;
4115                 }
4116                 continue;
4117             }
4118
4119             i++;                /* skip unparsed character and loop back */
4120         }
4121
4122         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4123 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4124 //          SendToPlayer(&buf[next_out], i - next_out);
4125             started != STARTED_HOLDINGS && leftover_start > next_out) {
4126             SendToPlayer(&buf[next_out], leftover_start - next_out);
4127             next_out = i;
4128         }
4129
4130         leftover_len = buf_len - leftover_start;
4131         /* if buffer ends with something we couldn't parse,
4132            reparse it after appending the next read */
4133
4134     } else if (count == 0) {
4135         RemoveInputSource(isr);
4136         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4137     } else {
4138         DisplayFatalError(_("Error reading from ICS"), error, 1);
4139     }
4140 }
4141
4142
4143 /* Board style 12 looks like this:
4144
4145    <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
4146
4147  * The "<12> " is stripped before it gets to this routine.  The two
4148  * trailing 0's (flip state and clock ticking) are later addition, and
4149  * some chess servers may not have them, or may have only the first.
4150  * Additional trailing fields may be added in the future.
4151  */
4152
4153 #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"
4154
4155 #define RELATION_OBSERVING_PLAYED    0
4156 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4157 #define RELATION_PLAYING_MYMOVE      1
4158 #define RELATION_PLAYING_NOTMYMOVE  -1
4159 #define RELATION_EXAMINING           2
4160 #define RELATION_ISOLATED_BOARD     -3
4161 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4162
4163 void
4164 ParseBoard12 (char *string)
4165 {
4166     GameMode newGameMode;
4167     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4168     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4169     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4170     char to_play, board_chars[200];
4171     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4172     char black[32], white[32];
4173     Board board;
4174     int prevMove = currentMove;
4175     int ticking = 2;
4176     ChessMove moveType;
4177     int fromX, fromY, toX, toY;
4178     char promoChar;
4179     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4180     char *bookHit = NULL; // [HGM] book
4181     Boolean weird = FALSE, reqFlag = FALSE;
4182
4183     fromX = fromY = toX = toY = -1;
4184
4185     newGame = FALSE;
4186
4187     if (appData.debugMode)
4188       fprintf(debugFP, _("Parsing board: %s\n"), string);
4189
4190     move_str[0] = NULLCHAR;
4191     elapsed_time[0] = NULLCHAR;
4192     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4193         int  i = 0, j;
4194         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4195             if(string[i] == ' ') { ranks++; files = 0; }
4196             else files++;
4197             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4198             i++;
4199         }
4200         for(j = 0; j <i; j++) board_chars[j] = string[j];
4201         board_chars[i] = '\0';
4202         string += i + 1;
4203     }
4204     n = sscanf(string, PATTERN, &to_play, &double_push,
4205                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4206                &gamenum, white, black, &relation, &basetime, &increment,
4207                &white_stren, &black_stren, &white_time, &black_time,
4208                &moveNum, str, elapsed_time, move_str, &ics_flip,
4209                &ticking);
4210
4211     if (n < 21) {
4212         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4213         DisplayError(str, 0);
4214         return;
4215     }
4216
4217     /* Convert the move number to internal form */
4218     moveNum = (moveNum - 1) * 2;
4219     if (to_play == 'B') moveNum++;
4220     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4221       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4222                         0, 1);
4223       return;
4224     }
4225
4226     switch (relation) {
4227       case RELATION_OBSERVING_PLAYED:
4228       case RELATION_OBSERVING_STATIC:
4229         if (gamenum == -1) {
4230             /* Old ICC buglet */
4231             relation = RELATION_OBSERVING_STATIC;
4232         }
4233         newGameMode = IcsObserving;
4234         break;
4235       case RELATION_PLAYING_MYMOVE:
4236       case RELATION_PLAYING_NOTMYMOVE:
4237         newGameMode =
4238           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4239             IcsPlayingWhite : IcsPlayingBlack;
4240         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4241         break;
4242       case RELATION_EXAMINING:
4243         newGameMode = IcsExamining;
4244         break;
4245       case RELATION_ISOLATED_BOARD:
4246       default:
4247         /* Just display this board.  If user was doing something else,
4248            we will forget about it until the next board comes. */
4249         newGameMode = IcsIdle;
4250         break;
4251       case RELATION_STARTING_POSITION:
4252         newGameMode = gameMode;
4253         break;
4254     }
4255
4256     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4257         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4258          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4259       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4260       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4261       static int lastBgGame = -1;
4262       char *toSqr;
4263       for (k = 0; k < ranks; k++) {
4264         for (j = 0; j < files; j++)
4265           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4266         if(gameInfo.holdingsWidth > 1) {
4267              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4268              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4269         }
4270       }
4271       CopyBoard(partnerBoard, board);
4272       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4273         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4274         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4275       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4276       if(toSqr = strchr(str, '-')) {
4277         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4278         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4279       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4280       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4281       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4282       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4283       if(twoBoards) {
4284           DisplayWhiteClock(white_time*fac, to_play == 'W');
4285           DisplayBlackClock(black_time*fac, to_play != 'W');
4286           activePartner = to_play;
4287           if(gamenum != lastBgGame) {
4288               char buf[MSG_SIZ];
4289               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4290               DisplayTitle(buf);
4291           }
4292           lastBgGame = gamenum;
4293           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4294                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4295       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4296                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4297       DisplayMessage(partnerStatus, "");
4298         partnerBoardValid = TRUE;
4299       return;
4300     }
4301
4302     if(appData.dualBoard && appData.bgObserve) {
4303         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4304             SendToICS(ics_prefix), SendToICS("pobserve\n");
4305         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4306             char buf[MSG_SIZ];
4307             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4308             SendToICS(buf);
4309         }
4310     }
4311
4312     /* Modify behavior for initial board display on move listing
4313        of wild games.
4314        */
4315     switch (ics_getting_history) {
4316       case H_FALSE:
4317       case H_REQUESTED:
4318         break;
4319       case H_GOT_REQ_HEADER:
4320       case H_GOT_UNREQ_HEADER:
4321         /* This is the initial position of the current game */
4322         gamenum = ics_gamenum;
4323         moveNum = 0;            /* old ICS bug workaround */
4324         if (to_play == 'B') {
4325           startedFromSetupPosition = TRUE;
4326           blackPlaysFirst = TRUE;
4327           moveNum = 1;
4328           if (forwardMostMove == 0) forwardMostMove = 1;
4329           if (backwardMostMove == 0) backwardMostMove = 1;
4330           if (currentMove == 0) currentMove = 1;
4331         }
4332         newGameMode = gameMode;
4333         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4334         break;
4335       case H_GOT_UNWANTED_HEADER:
4336         /* This is an initial board that we don't want */
4337         return;
4338       case H_GETTING_MOVES:
4339         /* Should not happen */
4340         DisplayError(_("Error gathering move list: extra board"), 0);
4341         ics_getting_history = H_FALSE;
4342         return;
4343     }
4344
4345    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4346                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4347                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4348      /* [HGM] We seem to have switched variant unexpectedly
4349       * Try to guess new variant from board size
4350       */
4351           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4352           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4353           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4354           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4355           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4356           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4357           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4358           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4359           /* Get a move list just to see the header, which
4360              will tell us whether this is really bug or zh */
4361           if (ics_getting_history == H_FALSE) {
4362             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4363             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4364             SendToICS(str);
4365           }
4366     }
4367
4368     /* Take action if this is the first board of a new game, or of a
4369        different game than is currently being displayed.  */
4370     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4371         relation == RELATION_ISOLATED_BOARD) {
4372
4373         /* Forget the old game and get the history (if any) of the new one */
4374         if (gameMode != BeginningOfGame) {
4375           Reset(TRUE, TRUE);
4376         }
4377         newGame = TRUE;
4378         if (appData.autoRaiseBoard) BoardToTop();
4379         prevMove = -3;
4380         if (gamenum == -1) {
4381             newGameMode = IcsIdle;
4382         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4383                    appData.getMoveList && !reqFlag) {
4384             /* Need to get game history */
4385             ics_getting_history = H_REQUESTED;
4386             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4387             SendToICS(str);
4388         }
4389
4390         /* Initially flip the board to have black on the bottom if playing
4391            black or if the ICS flip flag is set, but let the user change
4392            it with the Flip View button. */
4393         flipView = appData.autoFlipView ?
4394           (newGameMode == IcsPlayingBlack) || ics_flip :
4395           appData.flipView;
4396
4397         /* Done with values from previous mode; copy in new ones */
4398         gameMode = newGameMode;
4399         ModeHighlight();
4400         ics_gamenum = gamenum;
4401         if (gamenum == gs_gamenum) {
4402             int klen = strlen(gs_kind);
4403             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4404             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4405             gameInfo.event = StrSave(str);
4406         } else {
4407             gameInfo.event = StrSave("ICS game");
4408         }
4409         gameInfo.site = StrSave(appData.icsHost);
4410         gameInfo.date = PGNDate();
4411         gameInfo.round = StrSave("-");
4412         gameInfo.white = StrSave(white);
4413         gameInfo.black = StrSave(black);
4414         timeControl = basetime * 60 * 1000;
4415         timeControl_2 = 0;
4416         timeIncrement = increment * 1000;
4417         movesPerSession = 0;
4418         gameInfo.timeControl = TimeControlTagValue();
4419         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4420   if (appData.debugMode) {
4421     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4422     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4423     setbuf(debugFP, NULL);
4424   }
4425
4426         gameInfo.outOfBook = NULL;
4427
4428         /* Do we have the ratings? */
4429         if (strcmp(player1Name, white) == 0 &&
4430             strcmp(player2Name, black) == 0) {
4431             if (appData.debugMode)
4432               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4433                       player1Rating, player2Rating);
4434             gameInfo.whiteRating = player1Rating;
4435             gameInfo.blackRating = player2Rating;
4436         } else if (strcmp(player2Name, white) == 0 &&
4437                    strcmp(player1Name, black) == 0) {
4438             if (appData.debugMode)
4439               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4440                       player2Rating, player1Rating);
4441             gameInfo.whiteRating = player2Rating;
4442             gameInfo.blackRating = player1Rating;
4443         }
4444         player1Name[0] = player2Name[0] = NULLCHAR;
4445
4446         /* Silence shouts if requested */
4447         if (appData.quietPlay &&
4448             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4449             SendToICS(ics_prefix);
4450             SendToICS("set shout 0\n");
4451         }
4452     }
4453
4454     /* Deal with midgame name changes */
4455     if (!newGame) {
4456         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4457             if (gameInfo.white) free(gameInfo.white);
4458             gameInfo.white = StrSave(white);
4459         }
4460         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4461             if (gameInfo.black) free(gameInfo.black);
4462             gameInfo.black = StrSave(black);
4463         }
4464     }
4465
4466     /* Throw away game result if anything actually changes in examine mode */
4467     if (gameMode == IcsExamining && !newGame) {
4468         gameInfo.result = GameUnfinished;
4469         if (gameInfo.resultDetails != NULL) {
4470             free(gameInfo.resultDetails);
4471             gameInfo.resultDetails = NULL;
4472         }
4473     }
4474
4475     /* In pausing && IcsExamining mode, we ignore boards coming
4476        in if they are in a different variation than we are. */
4477     if (pauseExamInvalid) return;
4478     if (pausing && gameMode == IcsExamining) {
4479         if (moveNum <= pauseExamForwardMostMove) {
4480             pauseExamInvalid = TRUE;
4481             forwardMostMove = pauseExamForwardMostMove;
4482             return;
4483         }
4484     }
4485
4486   if (appData.debugMode) {
4487     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4488   }
4489     /* Parse the board */
4490     for (k = 0; k < ranks; k++) {
4491       for (j = 0; j < files; j++)
4492         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4493       if(gameInfo.holdingsWidth > 1) {
4494            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4495            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4496       }
4497     }
4498     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4499       board[5][BOARD_RGHT+1] = WhiteAngel;
4500       board[6][BOARD_RGHT+1] = WhiteMarshall;
4501       board[1][0] = BlackMarshall;
4502       board[2][0] = BlackAngel;
4503       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4504     }
4505     CopyBoard(boards[moveNum], board);
4506     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4507     if (moveNum == 0) {
4508         startedFromSetupPosition =
4509           !CompareBoards(board, initialPosition);
4510         if(startedFromSetupPosition)
4511             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4512     }
4513
4514     /* [HGM] Set castling rights. Take the outermost Rooks,
4515        to make it also work for FRC opening positions. Note that board12
4516        is really defective for later FRC positions, as it has no way to
4517        indicate which Rook can castle if they are on the same side of King.
4518        For the initial position we grant rights to the outermost Rooks,
4519        and remember thos rights, and we then copy them on positions
4520        later in an FRC game. This means WB might not recognize castlings with
4521        Rooks that have moved back to their original position as illegal,
4522        but in ICS mode that is not its job anyway.
4523     */
4524     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4525     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4526
4527         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4528             if(board[0][i] == WhiteRook) j = i;
4529         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4530         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4531             if(board[0][i] == WhiteRook) j = i;
4532         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4533         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4534             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4535         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4536         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4537             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4538         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4539
4540         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4541         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4542         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4543             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4544         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4545             if(board[BOARD_HEIGHT-1][k] == bKing)
4546                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4547         if(gameInfo.variant == VariantTwoKings) {
4548             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4549             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4550             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4551         }
4552     } else { int r;
4553         r = boards[moveNum][CASTLING][0] = initialRights[0];
4554         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4555         r = boards[moveNum][CASTLING][1] = initialRights[1];
4556         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4557         r = boards[moveNum][CASTLING][3] = initialRights[3];
4558         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4559         r = boards[moveNum][CASTLING][4] = initialRights[4];
4560         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4561         /* wildcastle kludge: always assume King has rights */
4562         r = boards[moveNum][CASTLING][2] = initialRights[2];
4563         r = boards[moveNum][CASTLING][5] = initialRights[5];
4564     }
4565     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4566     boards[moveNum][EP_STATUS] = EP_NONE;
4567     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4568     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4569     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4570
4571
4572     if (ics_getting_history == H_GOT_REQ_HEADER ||
4573         ics_getting_history == H_GOT_UNREQ_HEADER) {
4574         /* This was an initial position from a move list, not
4575            the current position */
4576         return;
4577     }
4578
4579     /* Update currentMove and known move number limits */
4580     newMove = newGame || moveNum > forwardMostMove;
4581
4582     if (newGame) {
4583         forwardMostMove = backwardMostMove = currentMove = moveNum;
4584         if (gameMode == IcsExamining && moveNum == 0) {
4585           /* Workaround for ICS limitation: we are not told the wild
4586              type when starting to examine a game.  But if we ask for
4587              the move list, the move list header will tell us */
4588             ics_getting_history = H_REQUESTED;
4589             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4590             SendToICS(str);
4591         }
4592     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4593                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4594 #if ZIPPY
4595         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4596         /* [HGM] applied this also to an engine that is silently watching        */
4597         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4598             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4599             gameInfo.variant == currentlyInitializedVariant) {
4600           takeback = forwardMostMove - moveNum;
4601           for (i = 0; i < takeback; i++) {
4602             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4603             SendToProgram("undo\n", &first);
4604           }
4605         }
4606 #endif
4607
4608         forwardMostMove = moveNum;
4609         if (!pausing || currentMove > forwardMostMove)
4610           currentMove = forwardMostMove;
4611     } else {
4612         /* New part of history that is not contiguous with old part */
4613         if (pausing && gameMode == IcsExamining) {
4614             pauseExamInvalid = TRUE;
4615             forwardMostMove = pauseExamForwardMostMove;
4616             return;
4617         }
4618         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4619 #if ZIPPY
4620             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4621                 // [HGM] when we will receive the move list we now request, it will be
4622                 // fed to the engine from the first move on. So if the engine is not
4623                 // in the initial position now, bring it there.
4624                 InitChessProgram(&first, 0);
4625             }
4626 #endif
4627             ics_getting_history = H_REQUESTED;
4628             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4629             SendToICS(str);
4630         }
4631         forwardMostMove = backwardMostMove = currentMove = moveNum;
4632     }
4633
4634     /* Update the clocks */
4635     if (strchr(elapsed_time, '.')) {
4636       /* Time is in ms */
4637       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4638       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4639     } else {
4640       /* Time is in seconds */
4641       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4642       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4643     }
4644
4645
4646 #if ZIPPY
4647     if (appData.zippyPlay && newGame &&
4648         gameMode != IcsObserving && gameMode != IcsIdle &&
4649         gameMode != IcsExamining)
4650       ZippyFirstBoard(moveNum, basetime, increment);
4651 #endif
4652
4653     /* Put the move on the move list, first converting
4654        to canonical algebraic form. */
4655     if (moveNum > 0) {
4656   if (appData.debugMode) {
4657     if (appData.debugMode) { int f = forwardMostMove;
4658         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4659                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4660                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4661     }
4662     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4663     fprintf(debugFP, "moveNum = %d\n", moveNum);
4664     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4665     setbuf(debugFP, NULL);
4666   }
4667         if (moveNum <= backwardMostMove) {
4668             /* We don't know what the board looked like before
4669                this move.  Punt. */
4670           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4671             strcat(parseList[moveNum - 1], " ");
4672             strcat(parseList[moveNum - 1], elapsed_time);
4673             moveList[moveNum - 1][0] = NULLCHAR;
4674         } else if (strcmp(move_str, "none") == 0) {
4675             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4676             /* Again, we don't know what the board looked like;
4677                this is really the start of the game. */
4678             parseList[moveNum - 1][0] = NULLCHAR;
4679             moveList[moveNum - 1][0] = NULLCHAR;
4680             backwardMostMove = moveNum;
4681             startedFromSetupPosition = TRUE;
4682             fromX = fromY = toX = toY = -1;
4683         } else {
4684           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4685           //                 So we parse the long-algebraic move string in stead of the SAN move
4686           int valid; char buf[MSG_SIZ], *prom;
4687
4688           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4689                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4690           // str looks something like "Q/a1-a2"; kill the slash
4691           if(str[1] == '/')
4692             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4693           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4694           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4695                 strcat(buf, prom); // long move lacks promo specification!
4696           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4697                 if(appData.debugMode)
4698                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4699                 safeStrCpy(move_str, buf, MSG_SIZ);
4700           }
4701           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4702                                 &fromX, &fromY, &toX, &toY, &promoChar)
4703                || ParseOneMove(buf, moveNum - 1, &moveType,
4704                                 &fromX, &fromY, &toX, &toY, &promoChar);
4705           // end of long SAN patch
4706           if (valid) {
4707             (void) CoordsToAlgebraic(boards[moveNum - 1],
4708                                      PosFlags(moveNum - 1),
4709                                      fromY, fromX, toY, toX, promoChar,
4710                                      parseList[moveNum-1]);
4711             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4712               case MT_NONE:
4713               case MT_STALEMATE:
4714               default:
4715                 break;
4716               case MT_CHECK:
4717                 if(gameInfo.variant != VariantShogi)
4718                     strcat(parseList[moveNum - 1], "+");
4719                 break;
4720               case MT_CHECKMATE:
4721               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4722                 strcat(parseList[moveNum - 1], "#");
4723                 break;
4724             }
4725             strcat(parseList[moveNum - 1], " ");
4726             strcat(parseList[moveNum - 1], elapsed_time);
4727             /* currentMoveString is set as a side-effect of ParseOneMove */
4728             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4729             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4730             strcat(moveList[moveNum - 1], "\n");
4731
4732             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4733                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4734               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4735                 ChessSquare old, new = boards[moveNum][k][j];
4736                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4737                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4738                   if(old == new) continue;
4739                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4740                   else if(new == WhiteWazir || new == BlackWazir) {
4741                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4742                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4743                       else boards[moveNum][k][j] = old; // preserve type of Gold
4744                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4745                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4746               }
4747           } else {
4748             /* Move from ICS was illegal!?  Punt. */
4749             if (appData.debugMode) {
4750               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4751               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4752             }
4753             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4754             strcat(parseList[moveNum - 1], " ");
4755             strcat(parseList[moveNum - 1], elapsed_time);
4756             moveList[moveNum - 1][0] = NULLCHAR;
4757             fromX = fromY = toX = toY = -1;
4758           }
4759         }
4760   if (appData.debugMode) {
4761     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4762     setbuf(debugFP, NULL);
4763   }
4764
4765 #if ZIPPY
4766         /* Send move to chess program (BEFORE animating it). */
4767         if (appData.zippyPlay && !newGame && newMove &&
4768            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4769
4770             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4771                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4772                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4773                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4774                             move_str);
4775                     DisplayError(str, 0);
4776                 } else {
4777                     if (first.sendTime) {
4778                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4779                     }
4780                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4781                     if (firstMove && !bookHit) {
4782                         firstMove = FALSE;
4783                         if (first.useColors) {
4784                           SendToProgram(gameMode == IcsPlayingWhite ?
4785                                         "white\ngo\n" :
4786                                         "black\ngo\n", &first);
4787                         } else {
4788                           SendToProgram("go\n", &first);
4789                         }
4790                         first.maybeThinking = TRUE;
4791                     }
4792                 }
4793             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4794               if (moveList[moveNum - 1][0] == NULLCHAR) {
4795                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4796                 DisplayError(str, 0);
4797               } else {
4798                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4799                 SendMoveToProgram(moveNum - 1, &first);
4800               }
4801             }
4802         }
4803 #endif
4804     }
4805
4806     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4807         /* If move comes from a remote source, animate it.  If it
4808            isn't remote, it will have already been animated. */
4809         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4810             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4811         }
4812         if (!pausing && appData.highlightLastMove) {
4813             SetHighlights(fromX, fromY, toX, toY);
4814         }
4815     }
4816
4817     /* Start the clocks */
4818     whiteFlag = blackFlag = FALSE;
4819     appData.clockMode = !(basetime == 0 && increment == 0);
4820     if (ticking == 0) {
4821       ics_clock_paused = TRUE;
4822       StopClocks();
4823     } else if (ticking == 1) {
4824       ics_clock_paused = FALSE;
4825     }
4826     if (gameMode == IcsIdle ||
4827         relation == RELATION_OBSERVING_STATIC ||
4828         relation == RELATION_EXAMINING ||
4829         ics_clock_paused)
4830       DisplayBothClocks();
4831     else
4832       StartClocks();
4833
4834     /* Display opponents and material strengths */
4835     if (gameInfo.variant != VariantBughouse &&
4836         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4837         if (tinyLayout || smallLayout) {
4838             if(gameInfo.variant == VariantNormal)
4839               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4840                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4841                     basetime, increment);
4842             else
4843               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4844                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4845                     basetime, increment, (int) gameInfo.variant);
4846         } else {
4847             if(gameInfo.variant == VariantNormal)
4848               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4849                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4850                     basetime, increment);
4851             else
4852               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4853                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4854                     basetime, increment, VariantName(gameInfo.variant));
4855         }
4856         DisplayTitle(str);
4857   if (appData.debugMode) {
4858     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4859   }
4860     }
4861
4862
4863     /* Display the board */
4864     if (!pausing && !appData.noGUI) {
4865
4866       if (appData.premove)
4867           if (!gotPremove ||
4868              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4869              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4870               ClearPremoveHighlights();
4871
4872       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4873         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4874       DrawPosition(j, boards[currentMove]);
4875
4876       DisplayMove(moveNum - 1);
4877       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4878             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4879               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4880         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4881       }
4882     }
4883
4884     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4885 #if ZIPPY
4886     if(bookHit) { // [HGM] book: simulate book reply
4887         static char bookMove[MSG_SIZ]; // a bit generous?
4888
4889         programStats.nodes = programStats.depth = programStats.time =
4890         programStats.score = programStats.got_only_move = 0;
4891         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4892
4893         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4894         strcat(bookMove, bookHit);
4895         HandleMachineMove(bookMove, &first);
4896     }
4897 #endif
4898 }
4899
4900 void
4901 GetMoveListEvent ()
4902 {
4903     char buf[MSG_SIZ];
4904     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4905         ics_getting_history = H_REQUESTED;
4906         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4907         SendToICS(buf);
4908     }
4909 }
4910
4911 void
4912 SendToBoth (char *msg)
4913 {   // to make it easy to keep two engines in step in dual analysis
4914     SendToProgram(msg, &first);
4915     if(second.analyzing) SendToProgram(msg, &second);
4916 }
4917
4918 void
4919 AnalysisPeriodicEvent (int force)
4920 {
4921     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4922          && !force) || !appData.periodicUpdates)
4923       return;
4924
4925     /* Send . command to Crafty to collect stats */
4926     SendToBoth(".\n");
4927
4928     /* Don't send another until we get a response (this makes
4929        us stop sending to old Crafty's which don't understand
4930        the "." command (sending illegal cmds resets node count & time,
4931        which looks bad)) */
4932     programStats.ok_to_send = 0;
4933 }
4934
4935 void
4936 ics_update_width (int new_width)
4937 {
4938         ics_printf("set width %d\n", new_width);
4939 }
4940
4941 void
4942 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4943 {
4944     char buf[MSG_SIZ];
4945
4946     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4947         // null move in variant where engine does not understand it (for analysis purposes)
4948         SendBoard(cps, moveNum + 1); // send position after move in stead.
4949         return;
4950     }
4951     if (cps->useUsermove) {
4952       SendToProgram("usermove ", cps);
4953     }
4954     if (cps->useSAN) {
4955       char *space;
4956       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4957         int len = space - parseList[moveNum];
4958         memcpy(buf, parseList[moveNum], len);
4959         buf[len++] = '\n';
4960         buf[len] = NULLCHAR;
4961       } else {
4962         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4963       }
4964       SendToProgram(buf, cps);
4965     } else {
4966       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4967         AlphaRank(moveList[moveNum], 4);
4968         SendToProgram(moveList[moveNum], cps);
4969         AlphaRank(moveList[moveNum], 4); // and back
4970       } else
4971       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4972        * the engine. It would be nice to have a better way to identify castle
4973        * moves here. */
4974       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4975                                                                          && cps->useOOCastle) {
4976         int fromX = moveList[moveNum][0] - AAA;
4977         int fromY = moveList[moveNum][1] - ONE;
4978         int toX = moveList[moveNum][2] - AAA;
4979         int toY = moveList[moveNum][3] - ONE;
4980         if((boards[moveNum][fromY][fromX] == WhiteKing
4981             && boards[moveNum][toY][toX] == WhiteRook)
4982            || (boards[moveNum][fromY][fromX] == BlackKing
4983                && boards[moveNum][toY][toX] == BlackRook)) {
4984           if(toX > fromX) SendToProgram("O-O\n", cps);
4985           else SendToProgram("O-O-O\n", cps);
4986         }
4987         else SendToProgram(moveList[moveNum], cps);
4988       } else
4989       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4990         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4991           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4992           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4993                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4994         } else
4995           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4996                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4997         SendToProgram(buf, cps);
4998       }
4999       else SendToProgram(moveList[moveNum], cps);
5000       /* End of additions by Tord */
5001     }
5002
5003     /* [HGM] setting up the opening has brought engine in force mode! */
5004     /*       Send 'go' if we are in a mode where machine should play. */
5005     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5006         (gameMode == TwoMachinesPlay   ||
5007 #if ZIPPY
5008          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5009 #endif
5010          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5011         SendToProgram("go\n", cps);
5012   if (appData.debugMode) {
5013     fprintf(debugFP, "(extra)\n");
5014   }
5015     }
5016     setboardSpoiledMachineBlack = 0;
5017 }
5018
5019 void
5020 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5021 {
5022     char user_move[MSG_SIZ];
5023     char suffix[4];
5024
5025     if(gameInfo.variant == VariantSChess && promoChar) {
5026         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5027         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5028     } else suffix[0] = NULLCHAR;
5029
5030     switch (moveType) {
5031       default:
5032         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5033                 (int)moveType, fromX, fromY, toX, toY);
5034         DisplayError(user_move + strlen("say "), 0);
5035         break;
5036       case WhiteKingSideCastle:
5037       case BlackKingSideCastle:
5038       case WhiteQueenSideCastleWild:
5039       case BlackQueenSideCastleWild:
5040       /* PUSH Fabien */
5041       case WhiteHSideCastleFR:
5042       case BlackHSideCastleFR:
5043       /* POP Fabien */
5044         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5045         break;
5046       case WhiteQueenSideCastle:
5047       case BlackQueenSideCastle:
5048       case WhiteKingSideCastleWild:
5049       case BlackKingSideCastleWild:
5050       /* PUSH Fabien */
5051       case WhiteASideCastleFR:
5052       case BlackASideCastleFR:
5053       /* POP Fabien */
5054         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5055         break;
5056       case WhiteNonPromotion:
5057       case BlackNonPromotion:
5058         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5059         break;
5060       case WhitePromotion:
5061       case BlackPromotion:
5062         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5063           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5064                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5065                 PieceToChar(WhiteFerz));
5066         else if(gameInfo.variant == VariantGreat)
5067           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5069                 PieceToChar(WhiteMan));
5070         else
5071           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5072                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5073                 promoChar);
5074         break;
5075       case WhiteDrop:
5076       case BlackDrop:
5077       drop:
5078         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5079                  ToUpper(PieceToChar((ChessSquare) fromX)),
5080                  AAA + toX, ONE + toY);
5081         break;
5082       case IllegalMove:  /* could be a variant we don't quite understand */
5083         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5084       case NormalMove:
5085       case WhiteCapturesEnPassant:
5086       case BlackCapturesEnPassant:
5087         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5088                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5089         break;
5090     }
5091     SendToICS(user_move);
5092     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5093         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5094 }
5095
5096 void
5097 UploadGameEvent ()
5098 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5099     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5100     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5101     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5102       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5103       return;
5104     }
5105     if(gameMode != IcsExamining) { // is this ever not the case?
5106         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5107
5108         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5109           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5110         } else { // on FICS we must first go to general examine mode
5111           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5112         }
5113         if(gameInfo.variant != VariantNormal) {
5114             // try figure out wild number, as xboard names are not always valid on ICS
5115             for(i=1; i<=36; i++) {
5116               snprintf(buf, MSG_SIZ, "wild/%d", i);
5117                 if(StringToVariant(buf) == gameInfo.variant) break;
5118             }
5119             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5120             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5121             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5122         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5123         SendToICS(ics_prefix);
5124         SendToICS(buf);
5125         if(startedFromSetupPosition || backwardMostMove != 0) {
5126           fen = PositionToFEN(backwardMostMove, NULL);
5127           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5128             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5129             SendToICS(buf);
5130           } else { // FICS: everything has to set by separate bsetup commands
5131             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5132             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5133             SendToICS(buf);
5134             if(!WhiteOnMove(backwardMostMove)) {
5135                 SendToICS("bsetup tomove black\n");
5136             }
5137             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5138             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5139             SendToICS(buf);
5140             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5141             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5142             SendToICS(buf);
5143             i = boards[backwardMostMove][EP_STATUS];
5144             if(i >= 0) { // set e.p.
5145               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5146                 SendToICS(buf);
5147             }
5148             bsetup++;
5149           }
5150         }
5151       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5152             SendToICS("bsetup done\n"); // switch to normal examining.
5153     }
5154     for(i = backwardMostMove; i<last; i++) {
5155         char buf[20];
5156         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5157         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5158             int len = strlen(moveList[i]);
5159             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5160             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5161         }
5162         SendToICS(buf);
5163     }
5164     SendToICS(ics_prefix);
5165     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5166 }
5167
5168 void
5169 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5170 {
5171     if (rf == DROP_RANK) {
5172       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5173       sprintf(move, "%c@%c%c\n",
5174                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5175     } else {
5176         if (promoChar == 'x' || promoChar == NULLCHAR) {
5177           sprintf(move, "%c%c%c%c\n",
5178                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5179         } else {
5180             sprintf(move, "%c%c%c%c%c\n",
5181                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5182         }
5183     }
5184 }
5185
5186 void
5187 ProcessICSInitScript (FILE *f)
5188 {
5189     char buf[MSG_SIZ];
5190
5191     while (fgets(buf, MSG_SIZ, f)) {
5192         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5193     }
5194
5195     fclose(f);
5196 }
5197
5198
5199 static int lastX, lastY, selectFlag, dragging;
5200
5201 void
5202 Sweep (int step)
5203 {
5204     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5205     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5206     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5207     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5208     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5209     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5210     do {
5211         promoSweep -= step;
5212         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5213         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5214         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5215         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5216         if(!step) step = -1;
5217     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5218             appData.testLegality && (promoSweep == king ||
5219             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5220     if(toX >= 0) {
5221         int victim = boards[currentMove][toY][toX];
5222         boards[currentMove][toY][toX] = promoSweep;
5223         DrawPosition(FALSE, boards[currentMove]);
5224         boards[currentMove][toY][toX] = victim;
5225     } else
5226     ChangeDragPiece(promoSweep);
5227 }
5228
5229 int
5230 PromoScroll (int x, int y)
5231 {
5232   int step = 0;
5233
5234   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5235   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5236   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5237   if(!step) return FALSE;
5238   lastX = x; lastY = y;
5239   if((promoSweep < BlackPawn) == flipView) step = -step;
5240   if(step > 0) selectFlag = 1;
5241   if(!selectFlag) Sweep(step);
5242   return FALSE;
5243 }
5244
5245 void
5246 NextPiece (int step)
5247 {
5248     ChessSquare piece = boards[currentMove][toY][toX];
5249     do {
5250         pieceSweep -= step;
5251         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5252         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5253         if(!step) step = -1;
5254     } while(PieceToChar(pieceSweep) == '.');
5255     boards[currentMove][toY][toX] = pieceSweep;
5256     DrawPosition(FALSE, boards[currentMove]);
5257     boards[currentMove][toY][toX] = piece;
5258 }
5259 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5260 void
5261 AlphaRank (char *move, int n)
5262 {
5263 //    char *p = move, c; int x, y;
5264
5265     if (appData.debugMode) {
5266         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5267     }
5268
5269     if(move[1]=='*' &&
5270        move[2]>='0' && move[2]<='9' &&
5271        move[3]>='a' && move[3]<='x'    ) {
5272         move[1] = '@';
5273         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5274         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5275     } else
5276     if(move[0]>='0' && move[0]<='9' &&
5277        move[1]>='a' && move[1]<='x' &&
5278        move[2]>='0' && move[2]<='9' &&
5279        move[3]>='a' && move[3]<='x'    ) {
5280         /* input move, Shogi -> normal */
5281         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5282         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5283         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5284         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5285     } else
5286     if(move[1]=='@' &&
5287        move[3]>='0' && move[3]<='9' &&
5288        move[2]>='a' && move[2]<='x'    ) {
5289         move[1] = '*';
5290         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5291         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5292     } else
5293     if(
5294        move[0]>='a' && move[0]<='x' &&
5295        move[3]>='0' && move[3]<='9' &&
5296        move[2]>='a' && move[2]<='x'    ) {
5297          /* output move, normal -> Shogi */
5298         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5299         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5300         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5301         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5302         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5303     }
5304     if (appData.debugMode) {
5305         fprintf(debugFP, "   out = '%s'\n", move);
5306     }
5307 }
5308
5309 char yy_textstr[8000];
5310
5311 /* Parser for moves from gnuchess, ICS, or user typein box */
5312 Boolean
5313 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5314 {
5315     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5316
5317     switch (*moveType) {
5318       case WhitePromotion:
5319       case BlackPromotion:
5320       case WhiteNonPromotion:
5321       case BlackNonPromotion:
5322       case NormalMove:
5323       case WhiteCapturesEnPassant:
5324       case BlackCapturesEnPassant:
5325       case WhiteKingSideCastle:
5326       case WhiteQueenSideCastle:
5327       case BlackKingSideCastle:
5328       case BlackQueenSideCastle:
5329       case WhiteKingSideCastleWild:
5330       case WhiteQueenSideCastleWild:
5331       case BlackKingSideCastleWild:
5332       case BlackQueenSideCastleWild:
5333       /* Code added by Tord: */
5334       case WhiteHSideCastleFR:
5335       case WhiteASideCastleFR:
5336       case BlackHSideCastleFR:
5337       case BlackASideCastleFR:
5338       /* End of code added by Tord */
5339       case IllegalMove:         /* bug or odd chess variant */
5340         *fromX = currentMoveString[0] - AAA;
5341         *fromY = currentMoveString[1] - ONE;
5342         *toX = currentMoveString[2] - AAA;
5343         *toY = currentMoveString[3] - ONE;
5344         *promoChar = currentMoveString[4];
5345         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5346             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5347     if (appData.debugMode) {
5348         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5349     }
5350             *fromX = *fromY = *toX = *toY = 0;
5351             return FALSE;
5352         }
5353         if (appData.testLegality) {
5354           return (*moveType != IllegalMove);
5355         } else {
5356           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5357                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5358         }
5359
5360       case WhiteDrop:
5361       case BlackDrop:
5362         *fromX = *moveType == WhiteDrop ?
5363           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5364           (int) CharToPiece(ToLower(currentMoveString[0]));
5365         *fromY = DROP_RANK;
5366         *toX = currentMoveString[2] - AAA;
5367         *toY = currentMoveString[3] - ONE;
5368         *promoChar = NULLCHAR;
5369         return TRUE;
5370
5371       case AmbiguousMove:
5372       case ImpossibleMove:
5373       case EndOfFile:
5374       case ElapsedTime:
5375       case Comment:
5376       case PGNTag:
5377       case NAG:
5378       case WhiteWins:
5379       case BlackWins:
5380       case GameIsDrawn:
5381       default:
5382     if (appData.debugMode) {
5383         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5384     }
5385         /* bug? */
5386         *fromX = *fromY = *toX = *toY = 0;
5387         *promoChar = NULLCHAR;
5388         return FALSE;
5389     }
5390 }
5391
5392 Boolean pushed = FALSE;
5393 char *lastParseAttempt;
5394
5395 void
5396 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5397 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5398   int fromX, fromY, toX, toY; char promoChar;
5399   ChessMove moveType;
5400   Boolean valid;
5401   int nr = 0;
5402
5403   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5404     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5405     pushed = TRUE;
5406   }
5407   endPV = forwardMostMove;
5408   do {
5409     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5410     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5411     lastParseAttempt = pv;
5412     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5413     if(!valid && nr == 0 &&
5414        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5415         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5416         // Hande case where played move is different from leading PV move
5417         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5418         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5419         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5420         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5421           endPV += 2; // if position different, keep this
5422           moveList[endPV-1][0] = fromX + AAA;
5423           moveList[endPV-1][1] = fromY + ONE;
5424           moveList[endPV-1][2] = toX + AAA;
5425           moveList[endPV-1][3] = toY + ONE;
5426           parseList[endPV-1][0] = NULLCHAR;
5427           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5428         }
5429       }
5430     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5431     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5432     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5433     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5434         valid++; // allow comments in PV
5435         continue;
5436     }
5437     nr++;
5438     if(endPV+1 > framePtr) break; // no space, truncate
5439     if(!valid) break;
5440     endPV++;
5441     CopyBoard(boards[endPV], boards[endPV-1]);
5442     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5443     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5444     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5445     CoordsToAlgebraic(boards[endPV - 1],
5446                              PosFlags(endPV - 1),
5447                              fromY, fromX, toY, toX, promoChar,
5448                              parseList[endPV - 1]);
5449   } while(valid);
5450   if(atEnd == 2) return; // used hidden, for PV conversion
5451   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5452   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5453   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5454                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5455   DrawPosition(TRUE, boards[currentMove]);
5456 }
5457
5458 int
5459 MultiPV (ChessProgramState *cps)
5460 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5461         int i;
5462         for(i=0; i<cps->nrOptions; i++)
5463             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5464                 return i;
5465         return -1;
5466 }
5467
5468 Boolean
5469 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5470 {
5471         int startPV, multi, lineStart, origIndex = index;
5472         char *p, buf2[MSG_SIZ];
5473         ChessProgramState *cps = (pane ? &second : &first);
5474
5475         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5476         lastX = x; lastY = y;
5477         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5478         lineStart = startPV = index;
5479         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5480         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5481         index = startPV;
5482         do{ while(buf[index] && buf[index] != '\n') index++;
5483         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5484         buf[index] = 0;
5485         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5486                 int n = cps->option[multi].value;
5487                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5488                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5489                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5490                 cps->option[multi].value = n;
5491                 *start = *end = 0;
5492                 return FALSE;
5493         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5494                 ExcludeClick(origIndex - lineStart);
5495                 return FALSE;
5496         }
5497         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5498         *start = startPV; *end = index-1;
5499         return TRUE;
5500 }
5501
5502 char *
5503 PvToSAN (char *pv)
5504 {
5505         static char buf[10*MSG_SIZ];
5506         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5507         *buf = NULLCHAR;
5508         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5509         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5510         for(i = forwardMostMove; i<endPV; i++){
5511             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5512             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5513             k += strlen(buf+k);
5514         }
5515         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5516         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5517         endPV = savedEnd;
5518         return buf;
5519 }
5520
5521 Boolean
5522 LoadPV (int x, int y)
5523 { // called on right mouse click to load PV
5524   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5525   lastX = x; lastY = y;
5526   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5527   return TRUE;
5528 }
5529
5530 void
5531 UnLoadPV ()
5532 {
5533   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5534   if(endPV < 0) return;
5535   if(appData.autoCopyPV) CopyFENToClipboard();
5536   endPV = -1;
5537   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5538         Boolean saveAnimate = appData.animate;
5539         if(pushed) {
5540             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5541                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5542             } else storedGames--; // abandon shelved tail of original game
5543         }
5544         pushed = FALSE;
5545         forwardMostMove = currentMove;
5546         currentMove = oldFMM;
5547         appData.animate = FALSE;
5548         ToNrEvent(forwardMostMove);
5549         appData.animate = saveAnimate;
5550   }
5551   currentMove = forwardMostMove;
5552   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5553   ClearPremoveHighlights();
5554   DrawPosition(TRUE, boards[currentMove]);
5555 }
5556
5557 void
5558 MovePV (int x, int y, int h)
5559 { // step through PV based on mouse coordinates (called on mouse move)
5560   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5561
5562   // we must somehow check if right button is still down (might be released off board!)
5563   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5564   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5565   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5566   if(!step) return;
5567   lastX = x; lastY = y;
5568
5569   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5570   if(endPV < 0) return;
5571   if(y < margin) step = 1; else
5572   if(y > h - margin) step = -1;
5573   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5574   currentMove += step;
5575   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5576   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5577                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5578   DrawPosition(FALSE, boards[currentMove]);
5579 }
5580
5581
5582 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5583 // All positions will have equal probability, but the current method will not provide a unique
5584 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5585 #define DARK 1
5586 #define LITE 2
5587 #define ANY 3
5588
5589 int squaresLeft[4];
5590 int piecesLeft[(int)BlackPawn];
5591 int seed, nrOfShuffles;
5592
5593 void
5594 GetPositionNumber ()
5595 {       // sets global variable seed
5596         int i;
5597
5598         seed = appData.defaultFrcPosition;
5599         if(seed < 0) { // randomize based on time for negative FRC position numbers
5600                 for(i=0; i<50; i++) seed += random();
5601                 seed = random() ^ random() >> 8 ^ random() << 8;
5602                 if(seed<0) seed = -seed;
5603         }
5604 }
5605
5606 int
5607 put (Board board, int pieceType, int rank, int n, int shade)
5608 // put the piece on the (n-1)-th empty squares of the given shade
5609 {
5610         int i;
5611
5612         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5613                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5614                         board[rank][i] = (ChessSquare) pieceType;
5615                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5616                         squaresLeft[ANY]--;
5617                         piecesLeft[pieceType]--;
5618                         return i;
5619                 }
5620         }
5621         return -1;
5622 }
5623
5624
5625 void
5626 AddOnePiece (Board board, int pieceType, int rank, int shade)
5627 // calculate where the next piece goes, (any empty square), and put it there
5628 {
5629         int i;
5630
5631         i = seed % squaresLeft[shade];
5632         nrOfShuffles *= squaresLeft[shade];
5633         seed /= squaresLeft[shade];
5634         put(board, pieceType, rank, i, shade);
5635 }
5636
5637 void
5638 AddTwoPieces (Board board, int pieceType, int rank)
5639 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5640 {
5641         int i, n=squaresLeft[ANY], j=n-1, k;
5642
5643         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5644         i = seed % k;  // pick one
5645         nrOfShuffles *= k;
5646         seed /= k;
5647         while(i >= j) i -= j--;
5648         j = n - 1 - j; i += j;
5649         put(board, pieceType, rank, j, ANY);
5650         put(board, pieceType, rank, i, ANY);
5651 }
5652
5653 void
5654 SetUpShuffle (Board board, int number)
5655 {
5656         int i, p, first=1;
5657
5658         GetPositionNumber(); nrOfShuffles = 1;
5659
5660         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5661         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5662         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5663
5664         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5665
5666         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5667             p = (int) board[0][i];
5668             if(p < (int) BlackPawn) piecesLeft[p] ++;
5669             board[0][i] = EmptySquare;
5670         }
5671
5672         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5673             // shuffles restricted to allow normal castling put KRR first
5674             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5675                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5676             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5677                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5678             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5679                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5680             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5681                 put(board, WhiteRook, 0, 0, ANY);
5682             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5683         }
5684
5685         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5686             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5687             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5688                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5689                 while(piecesLeft[p] >= 2) {
5690                     AddOnePiece(board, p, 0, LITE);
5691                     AddOnePiece(board, p, 0, DARK);
5692                 }
5693                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5694             }
5695
5696         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5697             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5698             // but we leave King and Rooks for last, to possibly obey FRC restriction
5699             if(p == (int)WhiteRook) continue;
5700             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5701             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5702         }
5703
5704         // now everything is placed, except perhaps King (Unicorn) and Rooks
5705
5706         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5707             // Last King gets castling rights
5708             while(piecesLeft[(int)WhiteUnicorn]) {
5709                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5710                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5711             }
5712
5713             while(piecesLeft[(int)WhiteKing]) {
5714                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5715                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5716             }
5717
5718
5719         } else {
5720             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5721             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5722         }
5723
5724         // Only Rooks can be left; simply place them all
5725         while(piecesLeft[(int)WhiteRook]) {
5726                 i = put(board, WhiteRook, 0, 0, ANY);
5727                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5728                         if(first) {
5729                                 first=0;
5730                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5731                         }
5732                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5733                 }
5734         }
5735         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5736             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5737         }
5738
5739         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5740 }
5741
5742 int
5743 SetCharTable (char *table, const char * map)
5744 /* [HGM] moved here from winboard.c because of its general usefulness */
5745 /*       Basically a safe strcpy that uses the last character as King */
5746 {
5747     int result = FALSE; int NrPieces;
5748
5749     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5750                     && NrPieces >= 12 && !(NrPieces&1)) {
5751         int i; /* [HGM] Accept even length from 12 to 34 */
5752
5753         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5754         for( i=0; i<NrPieces/2-1; i++ ) {
5755             table[i] = map[i];
5756             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5757         }
5758         table[(int) WhiteKing]  = map[NrPieces/2-1];
5759         table[(int) BlackKing]  = map[NrPieces-1];
5760
5761         result = TRUE;
5762     }
5763
5764     return result;
5765 }
5766
5767 void
5768 Prelude (Board board)
5769 {       // [HGM] superchess: random selection of exo-pieces
5770         int i, j, k; ChessSquare p;
5771         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5772
5773         GetPositionNumber(); // use FRC position number
5774
5775         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5776             SetCharTable(pieceToChar, appData.pieceToCharTable);
5777             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5778                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5779         }
5780
5781         j = seed%4;                 seed /= 4;
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 >= j); seed /= 3;
5786         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = 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%3;                 seed /= 3;
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%2 + (seed%2 >= j); seed /= 2;
5794         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5795         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5796         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5797         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5798         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5799         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5800         put(board, exoPieces[0],    0, 0, ANY);
5801         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5802 }
5803
5804 void
5805 InitPosition (int redraw)
5806 {
5807     ChessSquare (* pieces)[BOARD_FILES];
5808     int i, j, pawnRow, overrule,
5809     oldx = gameInfo.boardWidth,
5810     oldy = gameInfo.boardHeight,
5811     oldh = gameInfo.holdingsWidth;
5812     static int oldv;
5813
5814     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5815
5816     /* [AS] Initialize pv info list [HGM] and game status */
5817     {
5818         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5819             pvInfoList[i].depth = 0;
5820             boards[i][EP_STATUS] = EP_NONE;
5821             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5822         }
5823
5824         initialRulePlies = 0; /* 50-move counter start */
5825
5826         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5827         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5828     }
5829
5830
5831     /* [HGM] logic here is completely changed. In stead of full positions */
5832     /* the initialized data only consist of the two backranks. The switch */
5833     /* selects which one we will use, which is than copied to the Board   */
5834     /* initialPosition, which for the rest is initialized by Pawns and    */
5835     /* empty squares. This initial position is then copied to boards[0],  */
5836     /* possibly after shuffling, so that it remains available.            */
5837
5838     gameInfo.holdingsWidth = 0; /* default board sizes */
5839     gameInfo.boardWidth    = 8;
5840     gameInfo.boardHeight   = 8;
5841     gameInfo.holdingsSize  = 0;
5842     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5843     for(i=0; i<BOARD_FILES-2; i++)
5844       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5845     initialPosition[EP_STATUS] = EP_NONE;
5846     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5847     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5848          SetCharTable(pieceNickName, appData.pieceNickNames);
5849     else SetCharTable(pieceNickName, "............");
5850     pieces = FIDEArray;
5851
5852     switch (gameInfo.variant) {
5853     case VariantFischeRandom:
5854       shuffleOpenings = TRUE;
5855     default:
5856       break;
5857     case VariantShatranj:
5858       pieces = ShatranjArray;
5859       nrCastlingRights = 0;
5860       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5861       break;
5862     case VariantMakruk:
5863       pieces = makrukArray;
5864       nrCastlingRights = 0;
5865       startedFromSetupPosition = TRUE;
5866       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5867       break;
5868     case VariantTwoKings:
5869       pieces = twoKingsArray;
5870       break;
5871     case VariantGrand:
5872       pieces = GrandArray;
5873       nrCastlingRights = 0;
5874       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5875       gameInfo.boardWidth = 10;
5876       gameInfo.boardHeight = 10;
5877       gameInfo.holdingsSize = 7;
5878       break;
5879     case VariantCapaRandom:
5880       shuffleOpenings = TRUE;
5881     case VariantCapablanca:
5882       pieces = CapablancaArray;
5883       gameInfo.boardWidth = 10;
5884       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5885       break;
5886     case VariantGothic:
5887       pieces = GothicArray;
5888       gameInfo.boardWidth = 10;
5889       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5890       break;
5891     case VariantSChess:
5892       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5893       gameInfo.holdingsSize = 7;
5894       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5895       break;
5896     case VariantJanus:
5897       pieces = JanusArray;
5898       gameInfo.boardWidth = 10;
5899       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5900       nrCastlingRights = 6;
5901         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5902         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5903         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5904         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5905         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5906         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5907       break;
5908     case VariantFalcon:
5909       pieces = FalconArray;
5910       gameInfo.boardWidth = 10;
5911       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5912       break;
5913     case VariantXiangqi:
5914       pieces = XiangqiArray;
5915       gameInfo.boardWidth  = 9;
5916       gameInfo.boardHeight = 10;
5917       nrCastlingRights = 0;
5918       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5919       break;
5920     case VariantShogi:
5921       pieces = ShogiArray;
5922       gameInfo.boardWidth  = 9;
5923       gameInfo.boardHeight = 9;
5924       gameInfo.holdingsSize = 7;
5925       nrCastlingRights = 0;
5926       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5927       break;
5928     case VariantCourier:
5929       pieces = CourierArray;
5930       gameInfo.boardWidth  = 12;
5931       nrCastlingRights = 0;
5932       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5933       break;
5934     case VariantKnightmate:
5935       pieces = KnightmateArray;
5936       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5937       break;
5938     case VariantSpartan:
5939       pieces = SpartanArray;
5940       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5941       break;
5942     case VariantFairy:
5943       pieces = fairyArray;
5944       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5945       break;
5946     case VariantGreat:
5947       pieces = GreatArray;
5948       gameInfo.boardWidth = 10;
5949       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5950       gameInfo.holdingsSize = 8;
5951       break;
5952     case VariantSuper:
5953       pieces = FIDEArray;
5954       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5955       gameInfo.holdingsSize = 8;
5956       startedFromSetupPosition = TRUE;
5957       break;
5958     case VariantCrazyhouse:
5959     case VariantBughouse:
5960       pieces = FIDEArray;
5961       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5962       gameInfo.holdingsSize = 5;
5963       break;
5964     case VariantWildCastle:
5965       pieces = FIDEArray;
5966       /* !!?shuffle with kings guaranteed to be on d or e file */
5967       shuffleOpenings = 1;
5968       break;
5969     case VariantNoCastle:
5970       pieces = FIDEArray;
5971       nrCastlingRights = 0;
5972       /* !!?unconstrained back-rank shuffle */
5973       shuffleOpenings = 1;
5974       break;
5975     }
5976
5977     overrule = 0;
5978     if(appData.NrFiles >= 0) {
5979         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5980         gameInfo.boardWidth = appData.NrFiles;
5981     }
5982     if(appData.NrRanks >= 0) {
5983         gameInfo.boardHeight = appData.NrRanks;
5984     }
5985     if(appData.holdingsSize >= 0) {
5986         i = appData.holdingsSize;
5987         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5988         gameInfo.holdingsSize = i;
5989     }
5990     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5991     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5992         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5993
5994     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5995     if(pawnRow < 1) pawnRow = 1;
5996     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5997
5998     /* User pieceToChar list overrules defaults */
5999     if(appData.pieceToCharTable != NULL)
6000         SetCharTable(pieceToChar, appData.pieceToCharTable);
6001
6002     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6003
6004         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6005             s = (ChessSquare) 0; /* account holding counts in guard band */
6006         for( i=0; i<BOARD_HEIGHT; i++ )
6007             initialPosition[i][j] = s;
6008
6009         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6010         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6011         initialPosition[pawnRow][j] = WhitePawn;
6012         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6013         if(gameInfo.variant == VariantXiangqi) {
6014             if(j&1) {
6015                 initialPosition[pawnRow][j] =
6016                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6017                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6018                    initialPosition[2][j] = WhiteCannon;
6019                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6020                 }
6021             }
6022         }
6023         if(gameInfo.variant == VariantGrand) {
6024             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6025                initialPosition[0][j] = WhiteRook;
6026                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6027             }
6028         }
6029         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6030     }
6031     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6032
6033             j=BOARD_LEFT+1;
6034             initialPosition[1][j] = WhiteBishop;
6035             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6036             j=BOARD_RGHT-2;
6037             initialPosition[1][j] = WhiteRook;
6038             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6039     }
6040
6041     if( nrCastlingRights == -1) {
6042         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6043         /*       This sets default castling rights from none to normal corners   */
6044         /* Variants with other castling rights must set them themselves above    */
6045         nrCastlingRights = 6;
6046
6047         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6048         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6049         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6050         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6051         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6052         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6053      }
6054
6055      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6056      if(gameInfo.variant == VariantGreat) { // promotion commoners
6057         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6058         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6059         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6060         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6061      }
6062      if( gameInfo.variant == VariantSChess ) {
6063       initialPosition[1][0] = BlackMarshall;
6064       initialPosition[2][0] = BlackAngel;
6065       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6066       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6067       initialPosition[1][1] = initialPosition[2][1] = 
6068       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6069      }
6070   if (appData.debugMode) {
6071     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6072   }
6073     if(shuffleOpenings) {
6074         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6075         startedFromSetupPosition = TRUE;
6076     }
6077     if(startedFromPositionFile) {
6078       /* [HGM] loadPos: use PositionFile for every new game */
6079       CopyBoard(initialPosition, filePosition);
6080       for(i=0; i<nrCastlingRights; i++)
6081           initialRights[i] = filePosition[CASTLING][i];
6082       startedFromSetupPosition = TRUE;
6083     }
6084
6085     CopyBoard(boards[0], initialPosition);
6086
6087     if(oldx != gameInfo.boardWidth ||
6088        oldy != gameInfo.boardHeight ||
6089        oldv != gameInfo.variant ||
6090        oldh != gameInfo.holdingsWidth
6091                                          )
6092             InitDrawingSizes(-2 ,0);
6093
6094     oldv = gameInfo.variant;
6095     if (redraw)
6096       DrawPosition(TRUE, boards[currentMove]);
6097 }
6098
6099 void
6100 SendBoard (ChessProgramState *cps, int moveNum)
6101 {
6102     char message[MSG_SIZ];
6103
6104     if (cps->useSetboard) {
6105       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6106       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6107       SendToProgram(message, cps);
6108       free(fen);
6109
6110     } else {
6111       ChessSquare *bp;
6112       int i, j, left=0, right=BOARD_WIDTH;
6113       /* Kludge to set black to move, avoiding the troublesome and now
6114        * deprecated "black" command.
6115        */
6116       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6117         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6118
6119       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6120
6121       SendToProgram("edit\n", cps);
6122       SendToProgram("#\n", cps);
6123       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6124         bp = &boards[moveNum][i][left];
6125         for (j = left; j < right; j++, bp++) {
6126           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6127           if ((int) *bp < (int) BlackPawn) {
6128             if(j == BOARD_RGHT+1)
6129                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6130             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6131             if(message[0] == '+' || message[0] == '~') {
6132               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6133                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6134                         AAA + j, ONE + i);
6135             }
6136             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6137                 message[1] = BOARD_RGHT   - 1 - j + '1';
6138                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6139             }
6140             SendToProgram(message, cps);
6141           }
6142         }
6143       }
6144
6145       SendToProgram("c\n", cps);
6146       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6147         bp = &boards[moveNum][i][left];
6148         for (j = left; j < right; j++, bp++) {
6149           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6150           if (((int) *bp != (int) EmptySquare)
6151               && ((int) *bp >= (int) BlackPawn)) {
6152             if(j == BOARD_LEFT-2)
6153                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6154             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6155                     AAA + j, ONE + i);
6156             if(message[0] == '+' || message[0] == '~') {
6157               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6158                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6159                         AAA + j, ONE + i);
6160             }
6161             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6162                 message[1] = BOARD_RGHT   - 1 - j + '1';
6163                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6164             }
6165             SendToProgram(message, cps);
6166           }
6167         }
6168       }
6169
6170       SendToProgram(".\n", cps);
6171     }
6172     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6173 }
6174
6175 char exclusionHeader[MSG_SIZ];
6176 int exCnt, excludePtr;
6177 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6178 static Exclusion excluTab[200];
6179 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6180
6181 static void
6182 WriteMap (int s)
6183 {
6184     int j;
6185     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6186     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6187 }
6188
6189 static void
6190 ClearMap ()
6191 {
6192     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6193     excludePtr = 24; exCnt = 0;
6194     WriteMap(0);
6195 }
6196
6197 static void
6198 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6199 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6200     char buf[2*MOVE_LEN], *p;
6201     Exclusion *e = excluTab;
6202     int i;
6203     for(i=0; i<exCnt; i++)
6204         if(e[i].ff == fromX && e[i].fr == fromY &&
6205            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6206     if(i == exCnt) { // was not in exclude list; add it
6207         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6208         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6209             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6210             return; // abort
6211         }
6212         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6213         excludePtr++; e[i].mark = excludePtr++;
6214         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6215         exCnt++;
6216     }
6217     exclusionHeader[e[i].mark] = state;
6218 }
6219
6220 static int
6221 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6222 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6223     char buf[MSG_SIZ];
6224     int j, k;
6225     ChessMove moveType;
6226     if((signed char)promoChar == -1) { // kludge to indicate best move
6227         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6228             return 1; // if unparsable, abort
6229     }
6230     // update exclusion map (resolving toggle by consulting existing state)
6231     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6232     j = k%8; k >>= 3;
6233     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6234     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6235          excludeMap[k] |=   1<<j;
6236     else excludeMap[k] &= ~(1<<j);
6237     // update header
6238     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6239     // inform engine
6240     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6241     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6242     SendToBoth(buf);
6243     return (state == '+');
6244 }
6245
6246 static void
6247 ExcludeClick (int index)
6248 {
6249     int i, j;
6250     Exclusion *e = excluTab;
6251     if(index < 25) { // none, best or tail clicked
6252         if(index < 13) { // none: include all
6253             WriteMap(0); // clear map
6254             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6255             SendToBoth("include all\n"); // and inform engine
6256         } else if(index > 18) { // tail
6257             if(exclusionHeader[19] == '-') { // tail was excluded
6258                 SendToBoth("include all\n");
6259                 WriteMap(0); // clear map completely
6260                 // now re-exclude selected moves
6261                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6262                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6263             } else { // tail was included or in mixed state
6264                 SendToBoth("exclude all\n");
6265                 WriteMap(0xFF); // fill map completely
6266                 // now re-include selected moves
6267                 j = 0; // count them
6268                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6269                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6270                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6271             }
6272         } else { // best
6273             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6274         }
6275     } else {
6276         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6277             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6278             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6279             break;
6280         }
6281     }
6282 }
6283
6284 ChessSquare
6285 DefaultPromoChoice (int white)
6286 {
6287     ChessSquare result;
6288     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6289         result = WhiteFerz; // no choice
6290     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6291         result= WhiteKing; // in Suicide Q is the last thing we want
6292     else if(gameInfo.variant == VariantSpartan)
6293         result = white ? WhiteQueen : WhiteAngel;
6294     else result = WhiteQueen;
6295     if(!white) result = WHITE_TO_BLACK result;
6296     return result;
6297 }
6298
6299 static int autoQueen; // [HGM] oneclick
6300
6301 int
6302 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6303 {
6304     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6305     /* [HGM] add Shogi promotions */
6306     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6307     ChessSquare piece;
6308     ChessMove moveType;
6309     Boolean premove;
6310
6311     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6312     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6313
6314     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6315       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6316         return FALSE;
6317
6318     piece = boards[currentMove][fromY][fromX];
6319     if(gameInfo.variant == VariantShogi) {
6320         promotionZoneSize = BOARD_HEIGHT/3;
6321         highestPromotingPiece = (int)WhiteFerz;
6322     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6323         promotionZoneSize = 3;
6324     }
6325
6326     // Treat Lance as Pawn when it is not representing Amazon
6327     if(gameInfo.variant != VariantSuper) {
6328         if(piece == WhiteLance) piece = WhitePawn; else
6329         if(piece == BlackLance) piece = BlackPawn;
6330     }
6331
6332     // next weed out all moves that do not touch the promotion zone at all
6333     if((int)piece >= BlackPawn) {
6334         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6335              return FALSE;
6336         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6337     } else {
6338         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6339            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6340     }
6341
6342     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6343
6344     // weed out mandatory Shogi promotions
6345     if(gameInfo.variant == VariantShogi) {
6346         if(piece >= BlackPawn) {
6347             if(toY == 0 && piece == BlackPawn ||
6348                toY == 0 && piece == BlackQueen ||
6349                toY <= 1 && piece == BlackKnight) {
6350                 *promoChoice = '+';
6351                 return FALSE;
6352             }
6353         } else {
6354             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6355                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6356                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6357                 *promoChoice = '+';
6358                 return FALSE;
6359             }
6360         }
6361     }
6362
6363     // weed out obviously illegal Pawn moves
6364     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6365         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6366         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6367         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6368         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6369         // note we are not allowed to test for valid (non-)capture, due to premove
6370     }
6371
6372     // we either have a choice what to promote to, or (in Shogi) whether to promote
6373     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6374         *promoChoice = PieceToChar(BlackFerz);  // no choice
6375         return FALSE;
6376     }
6377     // no sense asking what we must promote to if it is going to explode...
6378     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6379         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6380         return FALSE;
6381     }
6382     // give caller the default choice even if we will not make it
6383     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6384     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6385     if(        sweepSelect && gameInfo.variant != VariantGreat
6386                            && gameInfo.variant != VariantGrand
6387                            && gameInfo.variant != VariantSuper) return FALSE;
6388     if(autoQueen) return FALSE; // predetermined
6389
6390     // suppress promotion popup on illegal moves that are not premoves
6391     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6392               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6393     if(appData.testLegality && !premove) {
6394         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6395                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6396         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6397             return FALSE;
6398     }
6399
6400     return TRUE;
6401 }
6402
6403 int
6404 InPalace (int row, int column)
6405 {   /* [HGM] for Xiangqi */
6406     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6407          column < (BOARD_WIDTH + 4)/2 &&
6408          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6409     return FALSE;
6410 }
6411
6412 int
6413 PieceForSquare (int x, int y)
6414 {
6415   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6416      return -1;
6417   else
6418      return boards[currentMove][y][x];
6419 }
6420
6421 int
6422 OKToStartUserMove (int x, int y)
6423 {
6424     ChessSquare from_piece;
6425     int white_piece;
6426
6427     if (matchMode) return FALSE;
6428     if (gameMode == EditPosition) return TRUE;
6429
6430     if (x >= 0 && y >= 0)
6431       from_piece = boards[currentMove][y][x];
6432     else
6433       from_piece = EmptySquare;
6434
6435     if (from_piece == EmptySquare) return FALSE;
6436
6437     white_piece = (int)from_piece >= (int)WhitePawn &&
6438       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6439
6440     switch (gameMode) {
6441       case AnalyzeFile:
6442       case TwoMachinesPlay:
6443       case EndOfGame:
6444         return FALSE;
6445
6446       case IcsObserving:
6447       case IcsIdle:
6448         return FALSE;
6449
6450       case MachinePlaysWhite:
6451       case IcsPlayingBlack:
6452         if (appData.zippyPlay) return FALSE;
6453         if (white_piece) {
6454             DisplayMoveError(_("You are playing Black"));
6455             return FALSE;
6456         }
6457         break;
6458
6459       case MachinePlaysBlack:
6460       case IcsPlayingWhite:
6461         if (appData.zippyPlay) return FALSE;
6462         if (!white_piece) {
6463             DisplayMoveError(_("You are playing White"));
6464             return FALSE;
6465         }
6466         break;
6467
6468       case PlayFromGameFile:
6469             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6470       case EditGame:
6471         if (!white_piece && WhiteOnMove(currentMove)) {
6472             DisplayMoveError(_("It is White's turn"));
6473             return FALSE;
6474         }
6475         if (white_piece && !WhiteOnMove(currentMove)) {
6476             DisplayMoveError(_("It is Black's turn"));
6477             return FALSE;
6478         }
6479         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6480             /* Editing correspondence game history */
6481             /* Could disallow this or prompt for confirmation */
6482             cmailOldMove = -1;
6483         }
6484         break;
6485
6486       case BeginningOfGame:
6487         if (appData.icsActive) return FALSE;
6488         if (!appData.noChessProgram) {
6489             if (!white_piece) {
6490                 DisplayMoveError(_("You are playing White"));
6491                 return FALSE;
6492             }
6493         }
6494         break;
6495
6496       case Training:
6497         if (!white_piece && WhiteOnMove(currentMove)) {
6498             DisplayMoveError(_("It is White's turn"));
6499             return FALSE;
6500         }
6501         if (white_piece && !WhiteOnMove(currentMove)) {
6502             DisplayMoveError(_("It is Black's turn"));
6503             return FALSE;
6504         }
6505         break;
6506
6507       default:
6508       case IcsExamining:
6509         break;
6510     }
6511     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6512         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6513         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6514         && gameMode != AnalyzeFile && gameMode != Training) {
6515         DisplayMoveError(_("Displayed position is not current"));
6516         return FALSE;
6517     }
6518     return TRUE;
6519 }
6520
6521 Boolean
6522 OnlyMove (int *x, int *y, Boolean captures) 
6523 {
6524     DisambiguateClosure cl;
6525     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6526     switch(gameMode) {
6527       case MachinePlaysBlack:
6528       case IcsPlayingWhite:
6529       case BeginningOfGame:
6530         if(!WhiteOnMove(currentMove)) return FALSE;
6531         break;
6532       case MachinePlaysWhite:
6533       case IcsPlayingBlack:
6534         if(WhiteOnMove(currentMove)) return FALSE;
6535         break;
6536       case EditGame:
6537         break;
6538       default:
6539         return FALSE;
6540     }
6541     cl.pieceIn = EmptySquare;
6542     cl.rfIn = *y;
6543     cl.ffIn = *x;
6544     cl.rtIn = -1;
6545     cl.ftIn = -1;
6546     cl.promoCharIn = NULLCHAR;
6547     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6548     if( cl.kind == NormalMove ||
6549         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6550         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6551         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6552       fromX = cl.ff;
6553       fromY = cl.rf;
6554       *x = cl.ft;
6555       *y = cl.rt;
6556       return TRUE;
6557     }
6558     if(cl.kind != ImpossibleMove) return FALSE;
6559     cl.pieceIn = EmptySquare;
6560     cl.rfIn = -1;
6561     cl.ffIn = -1;
6562     cl.rtIn = *y;
6563     cl.ftIn = *x;
6564     cl.promoCharIn = NULLCHAR;
6565     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6566     if( cl.kind == NormalMove ||
6567         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6568         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6569         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6570       fromX = cl.ff;
6571       fromY = cl.rf;
6572       *x = cl.ft;
6573       *y = cl.rt;
6574       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6575       return TRUE;
6576     }
6577     return FALSE;
6578 }
6579
6580 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6581 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6582 int lastLoadGameUseList = FALSE;
6583 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6584 ChessMove lastLoadGameStart = EndOfFile;
6585 int doubleClick;
6586
6587 void
6588 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6589 {
6590     ChessMove moveType;
6591     ChessSquare pup;
6592     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6593
6594     /* Check if the user is playing in turn.  This is complicated because we
6595        let the user "pick up" a piece before it is his turn.  So the piece he
6596        tried to pick up may have been captured by the time he puts it down!
6597        Therefore we use the color the user is supposed to be playing in this
6598        test, not the color of the piece that is currently on the starting
6599        square---except in EditGame mode, where the user is playing both
6600        sides; fortunately there the capture race can't happen.  (It can
6601        now happen in IcsExamining mode, but that's just too bad.  The user
6602        will get a somewhat confusing message in that case.)
6603        */
6604
6605     switch (gameMode) {
6606       case AnalyzeFile:
6607       case TwoMachinesPlay:
6608       case EndOfGame:
6609       case IcsObserving:
6610       case IcsIdle:
6611         /* We switched into a game mode where moves are not accepted,
6612            perhaps while the mouse button was down. */
6613         return;
6614
6615       case MachinePlaysWhite:
6616         /* User is moving for Black */
6617         if (WhiteOnMove(currentMove)) {
6618             DisplayMoveError(_("It is White's turn"));
6619             return;
6620         }
6621         break;
6622
6623       case MachinePlaysBlack:
6624         /* User is moving for White */
6625         if (!WhiteOnMove(currentMove)) {
6626             DisplayMoveError(_("It is Black's turn"));
6627             return;
6628         }
6629         break;
6630
6631       case PlayFromGameFile:
6632             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6633       case EditGame:
6634       case IcsExamining:
6635       case BeginningOfGame:
6636       case AnalyzeMode:
6637       case Training:
6638         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6639         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6640             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6641             /* User is moving for Black */
6642             if (WhiteOnMove(currentMove)) {
6643                 DisplayMoveError(_("It is White's turn"));
6644                 return;
6645             }
6646         } else {
6647             /* User is moving for White */
6648             if (!WhiteOnMove(currentMove)) {
6649                 DisplayMoveError(_("It is Black's turn"));
6650                 return;
6651             }
6652         }
6653         break;
6654
6655       case IcsPlayingBlack:
6656         /* User is moving for Black */
6657         if (WhiteOnMove(currentMove)) {
6658             if (!appData.premove) {
6659                 DisplayMoveError(_("It is White's turn"));
6660             } else if (toX >= 0 && toY >= 0) {
6661                 premoveToX = toX;
6662                 premoveToY = toY;
6663                 premoveFromX = fromX;
6664                 premoveFromY = fromY;
6665                 premovePromoChar = promoChar;
6666                 gotPremove = 1;
6667                 if (appData.debugMode)
6668                     fprintf(debugFP, "Got premove: fromX %d,"
6669                             "fromY %d, toX %d, toY %d\n",
6670                             fromX, fromY, toX, toY);
6671             }
6672             return;
6673         }
6674         break;
6675
6676       case IcsPlayingWhite:
6677         /* User is moving for White */
6678         if (!WhiteOnMove(currentMove)) {
6679             if (!appData.premove) {
6680                 DisplayMoveError(_("It is Black's turn"));
6681             } else if (toX >= 0 && toY >= 0) {
6682                 premoveToX = toX;
6683                 premoveToY = toY;
6684                 premoveFromX = fromX;
6685                 premoveFromY = fromY;
6686                 premovePromoChar = promoChar;
6687                 gotPremove = 1;
6688                 if (appData.debugMode)
6689                     fprintf(debugFP, "Got premove: fromX %d,"
6690                             "fromY %d, toX %d, toY %d\n",
6691                             fromX, fromY, toX, toY);
6692             }
6693             return;
6694         }
6695         break;
6696
6697       default:
6698         break;
6699
6700       case EditPosition:
6701         /* EditPosition, empty square, or different color piece;
6702            click-click move is possible */
6703         if (toX == -2 || toY == -2) {
6704             boards[0][fromY][fromX] = EmptySquare;
6705             DrawPosition(FALSE, boards[currentMove]);
6706             return;
6707         } else if (toX >= 0 && toY >= 0) {
6708             boards[0][toY][toX] = boards[0][fromY][fromX];
6709             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6710                 if(boards[0][fromY][0] != EmptySquare) {
6711                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6712                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6713                 }
6714             } else
6715             if(fromX == BOARD_RGHT+1) {
6716                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6717                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6718                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6719                 }
6720             } else
6721             boards[0][fromY][fromX] = gatingPiece;
6722             DrawPosition(FALSE, boards[currentMove]);
6723             return;
6724         }
6725         return;
6726     }
6727
6728     if(toX < 0 || toY < 0) return;
6729     pup = boards[currentMove][toY][toX];
6730
6731     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6732     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6733          if( pup != EmptySquare ) return;
6734          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6735            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6736                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6737            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6738            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6739            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6740            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6741          fromY = DROP_RANK;
6742     }
6743
6744     /* [HGM] always test for legality, to get promotion info */
6745     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6746                                          fromY, fromX, toY, toX, promoChar);
6747
6748     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6749
6750     /* [HGM] but possibly ignore an IllegalMove result */
6751     if (appData.testLegality) {
6752         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6753             DisplayMoveError(_("Illegal move"));
6754             return;
6755         }
6756     }
6757
6758     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6759         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6760              ClearPremoveHighlights(); // was included
6761         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6762         return;
6763     }
6764
6765     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6766 }
6767
6768 /* Common tail of UserMoveEvent and DropMenuEvent */
6769 int
6770 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6771 {
6772     char *bookHit = 0;
6773
6774     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6775         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6776         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6777         if(WhiteOnMove(currentMove)) {
6778             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6779         } else {
6780             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6781         }
6782     }
6783
6784     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6785        move type in caller when we know the move is a legal promotion */
6786     if(moveType == NormalMove && promoChar)
6787         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6788
6789     /* [HGM] <popupFix> The following if has been moved here from
6790        UserMoveEvent(). Because it seemed to belong here (why not allow
6791        piece drops in training games?), and because it can only be
6792        performed after it is known to what we promote. */
6793     if (gameMode == Training) {
6794       /* compare the move played on the board to the next move in the
6795        * game. If they match, display the move and the opponent's response.
6796        * If they don't match, display an error message.
6797        */
6798       int saveAnimate;
6799       Board testBoard;
6800       CopyBoard(testBoard, boards[currentMove]);
6801       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6802
6803       if (CompareBoards(testBoard, boards[currentMove+1])) {
6804         ForwardInner(currentMove+1);
6805
6806         /* Autoplay the opponent's response.
6807          * if appData.animate was TRUE when Training mode was entered,
6808          * the response will be animated.
6809          */
6810         saveAnimate = appData.animate;
6811         appData.animate = animateTraining;
6812         ForwardInner(currentMove+1);
6813         appData.animate = saveAnimate;
6814
6815         /* check for the end of the game */
6816         if (currentMove >= forwardMostMove) {
6817           gameMode = PlayFromGameFile;
6818           ModeHighlight();
6819           SetTrainingModeOff();
6820           DisplayInformation(_("End of game"));
6821         }
6822       } else {
6823         DisplayError(_("Incorrect move"), 0);
6824       }
6825       return 1;
6826     }
6827
6828   /* Ok, now we know that the move is good, so we can kill
6829      the previous line in Analysis Mode */
6830   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6831                                 && currentMove < forwardMostMove) {
6832     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6833     else forwardMostMove = currentMove;
6834   }
6835
6836   ClearMap();
6837
6838   /* If we need the chess program but it's dead, restart it */
6839   ResurrectChessProgram();
6840
6841   /* A user move restarts a paused game*/
6842   if (pausing)
6843     PauseEvent();
6844
6845   thinkOutput[0] = NULLCHAR;
6846
6847   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6848
6849   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6850     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6851     return 1;
6852   }
6853
6854   if (gameMode == BeginningOfGame) {
6855     if (appData.noChessProgram) {
6856       gameMode = EditGame;
6857       SetGameInfo();
6858     } else {
6859       char buf[MSG_SIZ];
6860       gameMode = MachinePlaysBlack;
6861       StartClocks();
6862       SetGameInfo();
6863       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6864       DisplayTitle(buf);
6865       if (first.sendName) {
6866         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6867         SendToProgram(buf, &first);
6868       }
6869       StartClocks();
6870     }
6871     ModeHighlight();
6872   }
6873
6874   /* Relay move to ICS or chess engine */
6875   if (appData.icsActive) {
6876     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6877         gameMode == IcsExamining) {
6878       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6879         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6880         SendToICS("draw ");
6881         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6882       }
6883       // also send plain move, in case ICS does not understand atomic claims
6884       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6885       ics_user_moved = 1;
6886     }
6887   } else {
6888     if (first.sendTime && (gameMode == BeginningOfGame ||
6889                            gameMode == MachinePlaysWhite ||
6890                            gameMode == MachinePlaysBlack)) {
6891       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6892     }
6893     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6894          // [HGM] book: if program might be playing, let it use book
6895         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6896         first.maybeThinking = TRUE;
6897     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6898         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6899         SendBoard(&first, currentMove+1);
6900         if(second.analyzing) {
6901             if(!second.useSetboard) SendToProgram("undo\n", &second);
6902             SendBoard(&second, currentMove+1);
6903         }
6904     } else {
6905         SendMoveToProgram(forwardMostMove-1, &first);
6906         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6907     }
6908     if (currentMove == cmailOldMove + 1) {
6909       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6910     }
6911   }
6912
6913   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6914
6915   switch (gameMode) {
6916   case EditGame:
6917     if(appData.testLegality)
6918     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6919     case MT_NONE:
6920     case MT_CHECK:
6921       break;
6922     case MT_CHECKMATE:
6923     case MT_STAINMATE:
6924       if (WhiteOnMove(currentMove)) {
6925         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6926       } else {
6927         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6928       }
6929       break;
6930     case MT_STALEMATE:
6931       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6932       break;
6933     }
6934     break;
6935
6936   case MachinePlaysBlack:
6937   case MachinePlaysWhite:
6938     /* disable certain menu options while machine is thinking */
6939     SetMachineThinkingEnables();
6940     break;
6941
6942   default:
6943     break;
6944   }
6945
6946   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6947   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6948
6949   if(bookHit) { // [HGM] book: simulate book reply
6950         static char bookMove[MSG_SIZ]; // a bit generous?
6951
6952         programStats.nodes = programStats.depth = programStats.time =
6953         programStats.score = programStats.got_only_move = 0;
6954         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6955
6956         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6957         strcat(bookMove, bookHit);
6958         HandleMachineMove(bookMove, &first);
6959   }
6960   return 1;
6961 }
6962
6963 void
6964 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6965 {
6966     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6967     Markers *m = (Markers *) closure;
6968     if(rf == fromY && ff == fromX)
6969         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6970                          || kind == WhiteCapturesEnPassant
6971                          || kind == BlackCapturesEnPassant);
6972     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6973 }
6974
6975 void
6976 MarkTargetSquares (int clear)
6977 {
6978   int x, y;
6979   if(clear) // no reason to ever suppress clearing
6980     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6981   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6982      !appData.testLegality || gameMode == EditPosition) return;
6983   if(!clear) {
6984     int capt = 0;
6985     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6986     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6987       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6988       if(capt)
6989       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6990     }
6991   }
6992   DrawPosition(FALSE, NULL);
6993 }
6994
6995 int
6996 Explode (Board board, int fromX, int fromY, int toX, int toY)
6997 {
6998     if(gameInfo.variant == VariantAtomic &&
6999        (board[toY][toX] != EmptySquare ||                     // capture?
7000         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7001                          board[fromY][fromX] == BlackPawn   )
7002       )) {
7003         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7004         return TRUE;
7005     }
7006     return FALSE;
7007 }
7008
7009 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7010
7011 int
7012 CanPromote (ChessSquare piece, int y)
7013 {
7014         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7015         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7016         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7017            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7018            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7019                                                   gameInfo.variant == VariantMakruk) return FALSE;
7020         return (piece == BlackPawn && y == 1 ||
7021                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7022                 piece == BlackLance && y == 1 ||
7023                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7024 }
7025
7026 void
7027 LeftClick (ClickType clickType, int xPix, int yPix)
7028 {
7029     int x, y;
7030     Boolean saveAnimate;
7031     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7032     char promoChoice = NULLCHAR;
7033     ChessSquare piece;
7034     static TimeMark lastClickTime, prevClickTime;
7035
7036     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7037
7038     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7039
7040     if (clickType == Press) ErrorPopDown();
7041
7042     x = EventToSquare(xPix, BOARD_WIDTH);
7043     y = EventToSquare(yPix, BOARD_HEIGHT);
7044     if (!flipView && y >= 0) {
7045         y = BOARD_HEIGHT - 1 - y;
7046     }
7047     if (flipView && x >= 0) {
7048         x = BOARD_WIDTH - 1 - x;
7049     }
7050
7051     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7052         defaultPromoChoice = promoSweep;
7053         promoSweep = EmptySquare;   // terminate sweep
7054         promoDefaultAltered = TRUE;
7055         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7056     }
7057
7058     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7059         if(clickType == Release) return; // ignore upclick of click-click destination
7060         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7061         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7062         if(gameInfo.holdingsWidth &&
7063                 (WhiteOnMove(currentMove)
7064                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7065                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7066             // click in right holdings, for determining promotion piece
7067             ChessSquare p = boards[currentMove][y][x];
7068             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7069             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7070             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7071                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7072                 fromX = fromY = -1;
7073                 return;
7074             }
7075         }
7076         DrawPosition(FALSE, boards[currentMove]);
7077         return;
7078     }
7079
7080     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7081     if(clickType == Press
7082             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7083               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7084               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7085         return;
7086
7087     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7088         // could be static click on premove from-square: abort premove
7089         gotPremove = 0;
7090         ClearPremoveHighlights();
7091     }
7092
7093     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7094         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7095
7096     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7097         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7098                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7099         defaultPromoChoice = DefaultPromoChoice(side);
7100     }
7101
7102     autoQueen = appData.alwaysPromoteToQueen;
7103
7104     if (fromX == -1) {
7105       int originalY = y;
7106       gatingPiece = EmptySquare;
7107       if (clickType != Press) {
7108         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7109             DragPieceEnd(xPix, yPix); dragging = 0;
7110             DrawPosition(FALSE, NULL);
7111         }
7112         return;
7113       }
7114       doubleClick = FALSE;
7115       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7116         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7117       }
7118       fromX = x; fromY = y; toX = toY = -1;
7119       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7120          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7121          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7122             /* First square */
7123             if (OKToStartUserMove(fromX, fromY)) {
7124                 second = 0;
7125                 MarkTargetSquares(0);
7126                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7127                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7128                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7129                     promoSweep = defaultPromoChoice;
7130                     selectFlag = 0; lastX = xPix; lastY = yPix;
7131                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7132                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7133                 }
7134                 if (appData.highlightDragging) {
7135                     SetHighlights(fromX, fromY, -1, -1);
7136                 } else {
7137                     ClearHighlights();
7138                 }
7139             } else fromX = fromY = -1;
7140             return;
7141         }
7142     }
7143
7144     /* fromX != -1 */
7145     if (clickType == Press && gameMode != EditPosition) {
7146         ChessSquare fromP;
7147         ChessSquare toP;
7148         int frc;
7149
7150         // ignore off-board to clicks
7151         if(y < 0 || x < 0) return;
7152
7153         /* Check if clicking again on the same color piece */
7154         fromP = boards[currentMove][fromY][fromX];
7155         toP = boards[currentMove][y][x];
7156         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7157         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7158              WhitePawn <= toP && toP <= WhiteKing &&
7159              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7160              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7161             (BlackPawn <= fromP && fromP <= BlackKing &&
7162              BlackPawn <= toP && toP <= BlackKing &&
7163              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7164              !(fromP == BlackKing && toP == BlackRook && frc))) {
7165             /* Clicked again on same color piece -- changed his mind */
7166             second = (x == fromX && y == fromY);
7167             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7168                 second = FALSE; // first double-click rather than scond click
7169                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7170             }
7171             promoDefaultAltered = FALSE;
7172             MarkTargetSquares(1);
7173            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7174             if (appData.highlightDragging) {
7175                 SetHighlights(x, y, -1, -1);
7176             } else {
7177                 ClearHighlights();
7178             }
7179             if (OKToStartUserMove(x, y)) {
7180                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7181                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7182                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7183                  gatingPiece = boards[currentMove][fromY][fromX];
7184                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7185                 fromX = x;
7186                 fromY = y; dragging = 1;
7187                 MarkTargetSquares(0);
7188                 DragPieceBegin(xPix, yPix, FALSE);
7189                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7190                     promoSweep = defaultPromoChoice;
7191                     selectFlag = 0; lastX = xPix; lastY = yPix;
7192                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7193                 }
7194             }
7195            }
7196            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7197            second = FALSE; 
7198         }
7199         // ignore clicks on holdings
7200         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7201     }
7202
7203     if (clickType == Release && x == fromX && y == fromY) {
7204         DragPieceEnd(xPix, yPix); dragging = 0;
7205         if(clearFlag) {
7206             // a deferred attempt to click-click move an empty square on top of a piece
7207             boards[currentMove][y][x] = EmptySquare;
7208             ClearHighlights();
7209             DrawPosition(FALSE, boards[currentMove]);
7210             fromX = fromY = -1; clearFlag = 0;
7211             return;
7212         }
7213         if (appData.animateDragging) {
7214             /* Undo animation damage if any */
7215             DrawPosition(FALSE, NULL);
7216         }
7217         if (second || sweepSelecting) {
7218             /* Second up/down in same square; just abort move */
7219             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7220             second = sweepSelecting = 0;
7221             fromX = fromY = -1;
7222             gatingPiece = EmptySquare;
7223             ClearHighlights();
7224             gotPremove = 0;
7225             ClearPremoveHighlights();
7226         } else {
7227             /* First upclick in same square; start click-click mode */
7228             SetHighlights(x, y, -1, -1);
7229         }
7230         return;
7231     }
7232
7233     clearFlag = 0;
7234
7235     /* we now have a different from- and (possibly off-board) to-square */
7236     /* Completed move */
7237     if(!sweepSelecting) {
7238         toX = x;
7239         toY = y;
7240     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7241
7242     saveAnimate = appData.animate;
7243     if (clickType == Press) {
7244         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7245             // must be Edit Position mode with empty-square selected
7246             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7247             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7248             return;
7249         }
7250         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7251           if(appData.sweepSelect) {
7252             ChessSquare piece = boards[currentMove][fromY][fromX];
7253             promoSweep = defaultPromoChoice;
7254             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7255             selectFlag = 0; lastX = xPix; lastY = yPix;
7256             Sweep(0); // Pawn that is going to promote: preview promotion piece
7257             sweepSelecting = 1;
7258             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7259             MarkTargetSquares(1);
7260           }
7261           return; // promo popup appears on up-click
7262         }
7263         /* Finish clickclick move */
7264         if (appData.animate || appData.highlightLastMove) {
7265             SetHighlights(fromX, fromY, toX, toY);
7266         } else {
7267             ClearHighlights();
7268         }
7269     } else {
7270 #if 0
7271 // [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
7272         /* Finish drag move */
7273         if (appData.highlightLastMove) {
7274             SetHighlights(fromX, fromY, toX, toY);
7275         } else {
7276             ClearHighlights();
7277         }
7278 #endif
7279         DragPieceEnd(xPix, yPix); dragging = 0;
7280         /* Don't animate move and drag both */
7281         appData.animate = FALSE;
7282     }
7283
7284     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7285     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7286         ChessSquare piece = boards[currentMove][fromY][fromX];
7287         if(gameMode == EditPosition && piece != EmptySquare &&
7288            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7289             int n;
7290
7291             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7292                 n = PieceToNumber(piece - (int)BlackPawn);
7293                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7294                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7295                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7296             } else
7297             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7298                 n = PieceToNumber(piece);
7299                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7300                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7301                 boards[currentMove][n][BOARD_WIDTH-2]++;
7302             }
7303             boards[currentMove][fromY][fromX] = EmptySquare;
7304         }
7305         ClearHighlights();
7306         fromX = fromY = -1;
7307         MarkTargetSquares(1);
7308         DrawPosition(TRUE, boards[currentMove]);
7309         return;
7310     }
7311
7312     // off-board moves should not be highlighted
7313     if(x < 0 || y < 0) ClearHighlights();
7314
7315     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7316
7317     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7318         SetHighlights(fromX, fromY, toX, toY);
7319         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7320             // [HGM] super: promotion to captured piece selected from holdings
7321             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7322             promotionChoice = TRUE;
7323             // kludge follows to temporarily execute move on display, without promoting yet
7324             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7325             boards[currentMove][toY][toX] = p;
7326             DrawPosition(FALSE, boards[currentMove]);
7327             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7328             boards[currentMove][toY][toX] = q;
7329             DisplayMessage("Click in holdings to choose piece", "");
7330             return;
7331         }
7332         PromotionPopUp();
7333     } else {
7334         int oldMove = currentMove;
7335         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7336         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7337         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7338         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7339            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7340             DrawPosition(TRUE, boards[currentMove]);
7341         MarkTargetSquares(1);
7342         fromX = fromY = -1;
7343     }
7344     appData.animate = saveAnimate;
7345     if (appData.animate || appData.animateDragging) {
7346         /* Undo animation damage if needed */
7347         DrawPosition(FALSE, NULL);
7348     }
7349 }
7350
7351 int
7352 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7353 {   // front-end-free part taken out of PieceMenuPopup
7354     int whichMenu; int xSqr, ySqr;
7355
7356     if(seekGraphUp) { // [HGM] seekgraph
7357         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7358         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7359         return -2;
7360     }
7361
7362     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7363          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7364         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7365         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7366         if(action == Press)   {
7367             originalFlip = flipView;
7368             flipView = !flipView; // temporarily flip board to see game from partners perspective
7369             DrawPosition(TRUE, partnerBoard);
7370             DisplayMessage(partnerStatus, "");
7371             partnerUp = TRUE;
7372         } else if(action == Release) {
7373             flipView = originalFlip;
7374             DrawPosition(TRUE, boards[currentMove]);
7375             partnerUp = FALSE;
7376         }
7377         return -2;
7378     }
7379
7380     xSqr = EventToSquare(x, BOARD_WIDTH);
7381     ySqr = EventToSquare(y, BOARD_HEIGHT);
7382     if (action == Release) {
7383         if(pieceSweep != EmptySquare) {
7384             EditPositionMenuEvent(pieceSweep, toX, toY);
7385             pieceSweep = EmptySquare;
7386         } else UnLoadPV(); // [HGM] pv
7387     }
7388     if (action != Press) return -2; // return code to be ignored
7389     switch (gameMode) {
7390       case IcsExamining:
7391         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7392       case EditPosition:
7393         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7394         if (xSqr < 0 || ySqr < 0) return -1;
7395         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7396         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7397         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7398         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7399         NextPiece(0);
7400         return 2; // grab
7401       case IcsObserving:
7402         if(!appData.icsEngineAnalyze) return -1;
7403       case IcsPlayingWhite:
7404       case IcsPlayingBlack:
7405         if(!appData.zippyPlay) goto noZip;
7406       case AnalyzeMode:
7407       case AnalyzeFile:
7408       case MachinePlaysWhite:
7409       case MachinePlaysBlack:
7410       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7411         if (!appData.dropMenu) {
7412           LoadPV(x, y);
7413           return 2; // flag front-end to grab mouse events
7414         }
7415         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7416            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7417       case EditGame:
7418       noZip:
7419         if (xSqr < 0 || ySqr < 0) return -1;
7420         if (!appData.dropMenu || appData.testLegality &&
7421             gameInfo.variant != VariantBughouse &&
7422             gameInfo.variant != VariantCrazyhouse) return -1;
7423         whichMenu = 1; // drop menu
7424         break;
7425       default:
7426         return -1;
7427     }
7428
7429     if (((*fromX = xSqr) < 0) ||
7430         ((*fromY = ySqr) < 0)) {
7431         *fromX = *fromY = -1;
7432         return -1;
7433     }
7434     if (flipView)
7435       *fromX = BOARD_WIDTH - 1 - *fromX;
7436     else
7437       *fromY = BOARD_HEIGHT - 1 - *fromY;
7438
7439     return whichMenu;
7440 }
7441
7442 void
7443 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7444 {
7445 //    char * hint = lastHint;
7446     FrontEndProgramStats stats;
7447
7448     stats.which = cps == &first ? 0 : 1;
7449     stats.depth = cpstats->depth;
7450     stats.nodes = cpstats->nodes;
7451     stats.score = cpstats->score;
7452     stats.time = cpstats->time;
7453     stats.pv = cpstats->movelist;
7454     stats.hint = lastHint;
7455     stats.an_move_index = 0;
7456     stats.an_move_count = 0;
7457
7458     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7459         stats.hint = cpstats->move_name;
7460         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7461         stats.an_move_count = cpstats->nr_moves;
7462     }
7463
7464     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
7465
7466     SetProgramStats( &stats );
7467 }
7468
7469 void
7470 ClearEngineOutputPane (int which)
7471 {
7472     static FrontEndProgramStats dummyStats;
7473     dummyStats.which = which;
7474     dummyStats.pv = "#";
7475     SetProgramStats( &dummyStats );
7476 }
7477
7478 #define MAXPLAYERS 500
7479
7480 char *
7481 TourneyStandings (int display)
7482 {
7483     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7484     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7485     char result, *p, *names[MAXPLAYERS];
7486
7487     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7488         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7489     names[0] = p = strdup(appData.participants);
7490     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7491
7492     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7493
7494     while(result = appData.results[nr]) {
7495         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7496         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7497         wScore = bScore = 0;
7498         switch(result) {
7499           case '+': wScore = 2; break;
7500           case '-': bScore = 2; break;
7501           case '=': wScore = bScore = 1; break;
7502           case ' ':
7503           case '*': return strdup("busy"); // tourney not finished
7504         }
7505         score[w] += wScore;
7506         score[b] += bScore;
7507         games[w]++;
7508         games[b]++;
7509         nr++;
7510     }
7511     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7512     for(w=0; w<nPlayers; w++) {
7513         bScore = -1;
7514         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7515         ranking[w] = b; points[w] = bScore; score[b] = -2;
7516     }
7517     p = malloc(nPlayers*34+1);
7518     for(w=0; w<nPlayers && w<display; w++)
7519         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7520     free(names[0]);
7521     return p;
7522 }
7523
7524 void
7525 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7526 {       // count all piece types
7527         int p, f, r;
7528         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7529         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7530         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7531                 p = board[r][f];
7532                 pCnt[p]++;
7533                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7534                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7535                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7536                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7537                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7538                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7539         }
7540 }
7541
7542 int
7543 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7544 {
7545         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7546         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7547
7548         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7549         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7550         if(myPawns == 2 && nMine == 3) // KPP
7551             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7552         if(myPawns == 1 && nMine == 2) // KP
7553             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7554         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7555             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7556         if(myPawns) return FALSE;
7557         if(pCnt[WhiteRook+side])
7558             return pCnt[BlackRook-side] ||
7559                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7560                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7561                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7562         if(pCnt[WhiteCannon+side]) {
7563             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7564             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7565         }
7566         if(pCnt[WhiteKnight+side])
7567             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7568         return FALSE;
7569 }
7570
7571 int
7572 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7573 {
7574         VariantClass v = gameInfo.variant;
7575
7576         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7577         if(v == VariantShatranj) return TRUE; // always winnable through baring
7578         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7579         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7580
7581         if(v == VariantXiangqi) {
7582                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7583
7584                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7585                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7586                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7587                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7588                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7589                 if(stale) // we have at least one last-rank P plus perhaps C
7590                     return majors // KPKX
7591                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7592                 else // KCA*E*
7593                     return pCnt[WhiteFerz+side] // KCAK
7594                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7595                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7596                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7597
7598         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7599                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7600
7601                 if(nMine == 1) return FALSE; // bare King
7602                 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
7603                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7604                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7605                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7606                 if(pCnt[WhiteKnight+side])
7607                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7608                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7609                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7610                 if(nBishops)
7611                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7612                 if(pCnt[WhiteAlfil+side])
7613                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7614                 if(pCnt[WhiteWazir+side])
7615                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7616         }
7617
7618         return TRUE;
7619 }
7620
7621 int
7622 CompareWithRights (Board b1, Board b2)
7623 {
7624     int rights = 0;
7625     if(!CompareBoards(b1, b2)) return FALSE;
7626     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7627     /* compare castling rights */
7628     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7629            rights++; /* King lost rights, while rook still had them */
7630     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7631         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7632            rights++; /* but at least one rook lost them */
7633     }
7634     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7635            rights++;
7636     if( b1[CASTLING][5] != NoRights ) {
7637         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7638            rights++;
7639     }
7640     return rights == 0;
7641 }
7642
7643 int
7644 Adjudicate (ChessProgramState *cps)
7645 {       // [HGM] some adjudications useful with buggy engines
7646         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7647         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7648         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7649         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7650         int k, count = 0; static int bare = 1;
7651         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7652         Boolean canAdjudicate = !appData.icsActive;
7653
7654         // most tests only when we understand the game, i.e. legality-checking on
7655             if( appData.testLegality )
7656             {   /* [HGM] Some more adjudications for obstinate engines */
7657                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7658                 static int moveCount = 6;
7659                 ChessMove result;
7660                 char *reason = NULL;
7661
7662                 /* Count what is on board. */
7663                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7664
7665                 /* Some material-based adjudications that have to be made before stalemate test */
7666                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7667                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7668                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7669                      if(canAdjudicate && appData.checkMates) {
7670                          if(engineOpponent)
7671                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7672                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7673                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7674                          return 1;
7675                      }
7676                 }
7677
7678                 /* Bare King in Shatranj (loses) or Losers (wins) */
7679                 if( nrW == 1 || nrB == 1) {
7680                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7681                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7682                      if(canAdjudicate && appData.checkMates) {
7683                          if(engineOpponent)
7684                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7685                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7686                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7687                          return 1;
7688                      }
7689                   } else
7690                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7691                   {    /* bare King */
7692                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7693                         if(canAdjudicate && appData.checkMates) {
7694                             /* but only adjudicate if adjudication enabled */
7695                             if(engineOpponent)
7696                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7697                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7698                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7699                             return 1;
7700                         }
7701                   }
7702                 } else bare = 1;
7703
7704
7705             // don't wait for engine to announce game end if we can judge ourselves
7706             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7707               case MT_CHECK:
7708                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7709                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7710                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7711                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7712                             checkCnt++;
7713                         if(checkCnt >= 2) {
7714                             reason = "Xboard adjudication: 3rd check";
7715                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7716                             break;
7717                         }
7718                     }
7719                 }
7720               case MT_NONE:
7721               default:
7722                 break;
7723               case MT_STALEMATE:
7724               case MT_STAINMATE:
7725                 reason = "Xboard adjudication: Stalemate";
7726                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7727                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7728                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7729                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7730                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7731                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7732                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7733                                                                         EP_CHECKMATE : EP_WINS);
7734                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7735                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7736                 }
7737                 break;
7738               case MT_CHECKMATE:
7739                 reason = "Xboard adjudication: Checkmate";
7740                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7741                 break;
7742             }
7743
7744                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7745                     case EP_STALEMATE:
7746                         result = GameIsDrawn; break;
7747                     case EP_CHECKMATE:
7748                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7749                     case EP_WINS:
7750                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7751                     default:
7752                         result = EndOfFile;
7753                 }
7754                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7755                     if(engineOpponent)
7756                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7757                     GameEnds( result, reason, GE_XBOARD );
7758                     return 1;
7759                 }
7760
7761                 /* Next absolutely insufficient mating material. */
7762                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7763                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7764                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7765
7766                      /* always flag draws, for judging claims */
7767                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7768
7769                      if(canAdjudicate && appData.materialDraws) {
7770                          /* but only adjudicate them if adjudication enabled */
7771                          if(engineOpponent) {
7772                            SendToProgram("force\n", engineOpponent); // suppress reply
7773                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7774                          }
7775                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7776                          return 1;
7777                      }
7778                 }
7779
7780                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7781                 if(gameInfo.variant == VariantXiangqi ?
7782                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7783                  : nrW + nrB == 4 &&
7784                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7785                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7786                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7787                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7788                    ) ) {
7789                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7790                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7791                           if(engineOpponent) {
7792                             SendToProgram("force\n", engineOpponent); // suppress reply
7793                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7794                           }
7795                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7796                           return 1;
7797                      }
7798                 } else moveCount = 6;
7799             }
7800
7801         // Repetition draws and 50-move rule can be applied independently of legality testing
7802
7803                 /* Check for rep-draws */
7804                 count = 0;
7805                 for(k = forwardMostMove-2;
7806                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7807                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7808                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7809                     k-=2)
7810                 {   int rights=0;
7811                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7812                         /* compare castling rights */
7813                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7814                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7815                                 rights++; /* King lost rights, while rook still had them */
7816                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7817                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7818                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7819                                    rights++; /* but at least one rook lost them */
7820                         }
7821                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7822                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7823                                 rights++;
7824                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7825                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7826                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7827                                    rights++;
7828                         }
7829                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7830                             && appData.drawRepeats > 1) {
7831                              /* adjudicate after user-specified nr of repeats */
7832                              int result = GameIsDrawn;
7833                              char *details = "XBoard adjudication: repetition draw";
7834                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7835                                 // [HGM] xiangqi: check for forbidden perpetuals
7836                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7837                                 for(m=forwardMostMove; m>k; m-=2) {
7838                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7839                                         ourPerpetual = 0; // the current mover did not always check
7840                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7841                                         hisPerpetual = 0; // the opponent did not always check
7842                                 }
7843                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7844                                                                         ourPerpetual, hisPerpetual);
7845                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7846                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7847                                     details = "Xboard adjudication: perpetual checking";
7848                                 } else
7849                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7850                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7851                                 } else
7852                                 // Now check for perpetual chases
7853                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7854                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7855                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7856                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7857                                         static char resdet[MSG_SIZ];
7858                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7859                                         details = resdet;
7860                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7861                                     } else
7862                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7863                                         break; // Abort repetition-checking loop.
7864                                 }
7865                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7866                              }
7867                              if(engineOpponent) {
7868                                SendToProgram("force\n", engineOpponent); // suppress reply
7869                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7870                              }
7871                              GameEnds( result, details, GE_XBOARD );
7872                              return 1;
7873                         }
7874                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7875                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7876                     }
7877                 }
7878
7879                 /* Now we test for 50-move draws. Determine ply count */
7880                 count = forwardMostMove;
7881                 /* look for last irreversble move */
7882                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7883                     count--;
7884                 /* if we hit starting position, add initial plies */
7885                 if( count == backwardMostMove )
7886                     count -= initialRulePlies;
7887                 count = forwardMostMove - count;
7888                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7889                         // adjust reversible move counter for checks in Xiangqi
7890                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7891                         if(i < backwardMostMove) i = backwardMostMove;
7892                         while(i <= forwardMostMove) {
7893                                 lastCheck = inCheck; // check evasion does not count
7894                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7895                                 if(inCheck || lastCheck) count--; // check does not count
7896                                 i++;
7897                         }
7898                 }
7899                 if( count >= 100)
7900                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7901                          /* this is used to judge if draw claims are legal */
7902                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7903                          if(engineOpponent) {
7904                            SendToProgram("force\n", engineOpponent); // suppress reply
7905                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7906                          }
7907                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7908                          return 1;
7909                 }
7910
7911                 /* if draw offer is pending, treat it as a draw claim
7912                  * when draw condition present, to allow engines a way to
7913                  * claim draws before making their move to avoid a race
7914                  * condition occurring after their move
7915                  */
7916                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7917                          char *p = NULL;
7918                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7919                              p = "Draw claim: 50-move rule";
7920                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7921                              p = "Draw claim: 3-fold repetition";
7922                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7923                              p = "Draw claim: insufficient mating material";
7924                          if( p != NULL && canAdjudicate) {
7925                              if(engineOpponent) {
7926                                SendToProgram("force\n", engineOpponent); // suppress reply
7927                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7928                              }
7929                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7930                              return 1;
7931                          }
7932                 }
7933
7934                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7935                     if(engineOpponent) {
7936                       SendToProgram("force\n", engineOpponent); // suppress reply
7937                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7938                     }
7939                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7940                     return 1;
7941                 }
7942         return 0;
7943 }
7944
7945 char *
7946 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7947 {   // [HGM] book: this routine intercepts moves to simulate book replies
7948     char *bookHit = NULL;
7949
7950     //first determine if the incoming move brings opponent into his book
7951     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7952         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7953     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7954     if(bookHit != NULL && !cps->bookSuspend) {
7955         // make sure opponent is not going to reply after receiving move to book position
7956         SendToProgram("force\n", cps);
7957         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7958     }
7959     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7960     // now arrange restart after book miss
7961     if(bookHit) {
7962         // after a book hit we never send 'go', and the code after the call to this routine
7963         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7964         char buf[MSG_SIZ], *move = bookHit;
7965         if(cps->useSAN) {
7966             int fromX, fromY, toX, toY;
7967             char promoChar;
7968             ChessMove moveType;
7969             move = buf + 30;
7970             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7971                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7972                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7973                                     PosFlags(forwardMostMove),
7974                                     fromY, fromX, toY, toX, promoChar, move);
7975             } else {
7976                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7977                 bookHit = NULL;
7978             }
7979         }
7980         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7981         SendToProgram(buf, cps);
7982         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7983     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7984         SendToProgram("go\n", cps);
7985         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7986     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7987         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7988             SendToProgram("go\n", cps);
7989         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7990     }
7991     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7992 }
7993
7994 int
7995 LoadError (char *errmess, ChessProgramState *cps)
7996 {   // unloads engine and switches back to -ncp mode if it was first
7997     if(cps->initDone) return FALSE;
7998     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7999     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8000     cps->pr = NoProc; 
8001     if(cps == &first) {
8002         appData.noChessProgram = TRUE;
8003         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8004         gameMode = BeginningOfGame; ModeHighlight();
8005         SetNCPMode();
8006     }
8007     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8008     DisplayMessage("", ""); // erase waiting message
8009     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8010     return TRUE;
8011 }
8012
8013 char *savedMessage;
8014 ChessProgramState *savedState;
8015 void
8016 DeferredBookMove (void)
8017 {
8018         if(savedState->lastPing != savedState->lastPong)
8019                     ScheduleDelayedEvent(DeferredBookMove, 10);
8020         else
8021         HandleMachineMove(savedMessage, savedState);
8022 }
8023
8024 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8025
8026 void
8027 HandleMachineMove (char *message, ChessProgramState *cps)
8028 {
8029     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8030     char realname[MSG_SIZ];
8031     int fromX, fromY, toX, toY;
8032     ChessMove moveType;
8033     char promoChar;
8034     char *p, *pv=buf1;
8035     int machineWhite, oldError;
8036     char *bookHit;
8037
8038     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8039         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8040         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8041             DisplayError(_("Invalid pairing from pairing engine"), 0);
8042             return;
8043         }
8044         pairingReceived = 1;
8045         NextMatchGame();
8046         return; // Skim the pairing messages here.
8047     }
8048
8049     oldError = cps->userError; cps->userError = 0;
8050
8051 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8052     /*
8053      * Kludge to ignore BEL characters
8054      */
8055     while (*message == '\007') message++;
8056
8057     /*
8058      * [HGM] engine debug message: ignore lines starting with '#' character
8059      */
8060     if(cps->debug && *message == '#') return;
8061
8062     /*
8063      * Look for book output
8064      */
8065     if (cps == &first && bookRequested) {
8066         if (message[0] == '\t' || message[0] == ' ') {
8067             /* Part of the book output is here; append it */
8068             strcat(bookOutput, message);
8069             strcat(bookOutput, "  \n");
8070             return;
8071         } else if (bookOutput[0] != NULLCHAR) {
8072             /* All of book output has arrived; display it */
8073             char *p = bookOutput;
8074             while (*p != NULLCHAR) {
8075                 if (*p == '\t') *p = ' ';
8076                 p++;
8077             }
8078             DisplayInformation(bookOutput);
8079             bookRequested = FALSE;
8080             /* Fall through to parse the current output */
8081         }
8082     }
8083
8084     /*
8085      * Look for machine move.
8086      */
8087     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8088         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8089     {
8090         /* This method is only useful on engines that support ping */
8091         if (cps->lastPing != cps->lastPong) {
8092           if (gameMode == BeginningOfGame) {
8093             /* Extra move from before last new; ignore */
8094             if (appData.debugMode) {
8095                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8096             }
8097           } else {
8098             if (appData.debugMode) {
8099                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8100                         cps->which, gameMode);
8101             }
8102
8103             SendToProgram("undo\n", cps);
8104           }
8105           return;
8106         }
8107
8108         switch (gameMode) {
8109           case BeginningOfGame:
8110             /* Extra move from before last reset; ignore */
8111             if (appData.debugMode) {
8112                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8113             }
8114             return;
8115
8116           case EndOfGame:
8117           case IcsIdle:
8118           default:
8119             /* Extra move after we tried to stop.  The mode test is
8120                not a reliable way of detecting this problem, but it's
8121                the best we can do on engines that don't support ping.
8122             */
8123             if (appData.debugMode) {
8124                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8125                         cps->which, gameMode);
8126             }
8127             SendToProgram("undo\n", cps);
8128             return;
8129
8130           case MachinePlaysWhite:
8131           case IcsPlayingWhite:
8132             machineWhite = TRUE;
8133             break;
8134
8135           case MachinePlaysBlack:
8136           case IcsPlayingBlack:
8137             machineWhite = FALSE;
8138             break;
8139
8140           case TwoMachinesPlay:
8141             machineWhite = (cps->twoMachinesColor[0] == 'w');
8142             break;
8143         }
8144         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8145             if (appData.debugMode) {
8146                 fprintf(debugFP,
8147                         "Ignoring move out of turn by %s, gameMode %d"
8148                         ", forwardMost %d\n",
8149                         cps->which, gameMode, forwardMostMove);
8150             }
8151             return;
8152         }
8153
8154         if(cps->alphaRank) AlphaRank(machineMove, 4);
8155         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8156                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8157             /* Machine move could not be parsed; ignore it. */
8158           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8159                     machineMove, _(cps->which));
8160             DisplayError(buf1, 0);
8161             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8162                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8163             if (gameMode == TwoMachinesPlay) {
8164               GameEnds(machineWhite ? BlackWins : WhiteWins,
8165                        buf1, GE_XBOARD);
8166             }
8167             return;
8168         }
8169
8170         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8171         /* So we have to redo legality test with true e.p. status here,  */
8172         /* to make sure an illegal e.p. capture does not slip through,   */
8173         /* to cause a forfeit on a justified illegal-move complaint      */
8174         /* of the opponent.                                              */
8175         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8176            ChessMove moveType;
8177            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8178                              fromY, fromX, toY, toX, promoChar);
8179             if(moveType == IllegalMove) {
8180               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8181                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8182                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8183                            buf1, GE_XBOARD);
8184                 return;
8185            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8186            /* [HGM] Kludge to handle engines that send FRC-style castling
8187               when they shouldn't (like TSCP-Gothic) */
8188            switch(moveType) {
8189              case WhiteASideCastleFR:
8190              case BlackASideCastleFR:
8191                toX+=2;
8192                currentMoveString[2]++;
8193                break;
8194              case WhiteHSideCastleFR:
8195              case BlackHSideCastleFR:
8196                toX--;
8197                currentMoveString[2]--;
8198                break;
8199              default: ; // nothing to do, but suppresses warning of pedantic compilers
8200            }
8201         }
8202         hintRequested = FALSE;
8203         lastHint[0] = NULLCHAR;
8204         bookRequested = FALSE;
8205         /* Program may be pondering now */
8206         cps->maybeThinking = TRUE;
8207         if (cps->sendTime == 2) cps->sendTime = 1;
8208         if (cps->offeredDraw) cps->offeredDraw--;
8209
8210         /* [AS] Save move info*/
8211         pvInfoList[ forwardMostMove ].score = programStats.score;
8212         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8213         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8214
8215         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8216
8217         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8218         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8219             int count = 0;
8220
8221             while( count < adjudicateLossPlies ) {
8222                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8223
8224                 if( count & 1 ) {
8225                     score = -score; /* Flip score for winning side */
8226                 }
8227
8228                 if( score > adjudicateLossThreshold ) {
8229                     break;
8230                 }
8231
8232                 count++;
8233             }
8234
8235             if( count >= adjudicateLossPlies ) {
8236                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8237
8238                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8239                     "Xboard adjudication",
8240                     GE_XBOARD );
8241
8242                 return;
8243             }
8244         }
8245
8246         if(Adjudicate(cps)) {
8247             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8248             return; // [HGM] adjudicate: for all automatic game ends
8249         }
8250
8251 #if ZIPPY
8252         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8253             first.initDone) {
8254           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8255                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8256                 SendToICS("draw ");
8257                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8258           }
8259           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8260           ics_user_moved = 1;
8261           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8262                 char buf[3*MSG_SIZ];
8263
8264                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8265                         programStats.score / 100.,
8266                         programStats.depth,
8267                         programStats.time / 100.,
8268                         (unsigned int)programStats.nodes,
8269                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8270                         programStats.movelist);
8271                 SendToICS(buf);
8272 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8273           }
8274         }
8275 #endif
8276
8277         /* [AS] Clear stats for next move */
8278         ClearProgramStats();
8279         thinkOutput[0] = NULLCHAR;
8280         hiddenThinkOutputState = 0;
8281
8282         bookHit = NULL;
8283         if (gameMode == TwoMachinesPlay) {
8284             /* [HGM] relaying draw offers moved to after reception of move */
8285             /* and interpreting offer as claim if it brings draw condition */
8286             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8287                 SendToProgram("draw\n", cps->other);
8288             }
8289             if (cps->other->sendTime) {
8290                 SendTimeRemaining(cps->other,
8291                                   cps->other->twoMachinesColor[0] == 'w');
8292             }
8293             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8294             if (firstMove && !bookHit) {
8295                 firstMove = FALSE;
8296                 if (cps->other->useColors) {
8297                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8298                 }
8299                 SendToProgram("go\n", cps->other);
8300             }
8301             cps->other->maybeThinking = TRUE;
8302         }
8303
8304         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8305
8306         if (!pausing && appData.ringBellAfterMoves) {
8307             RingBell();
8308         }
8309
8310         /*
8311          * Reenable menu items that were disabled while
8312          * machine was thinking
8313          */
8314         if (gameMode != TwoMachinesPlay)
8315             SetUserThinkingEnables();
8316
8317         // [HGM] book: after book hit opponent has received move and is now in force mode
8318         // force the book reply into it, and then fake that it outputted this move by jumping
8319         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8320         if(bookHit) {
8321                 static char bookMove[MSG_SIZ]; // a bit generous?
8322
8323                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8324                 strcat(bookMove, bookHit);
8325                 message = bookMove;
8326                 cps = cps->other;
8327                 programStats.nodes = programStats.depth = programStats.time =
8328                 programStats.score = programStats.got_only_move = 0;
8329                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8330
8331                 if(cps->lastPing != cps->lastPong) {
8332                     savedMessage = message; // args for deferred call
8333                     savedState = cps;
8334                     ScheduleDelayedEvent(DeferredBookMove, 10);
8335                     return;
8336                 }
8337                 goto FakeBookMove;
8338         }
8339
8340         return;
8341     }
8342
8343     /* Set special modes for chess engines.  Later something general
8344      *  could be added here; for now there is just one kludge feature,
8345      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8346      *  when "xboard" is given as an interactive command.
8347      */
8348     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8349         cps->useSigint = FALSE;
8350         cps->useSigterm = FALSE;
8351     }
8352     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8353       ParseFeatures(message+8, cps);
8354       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8355     }
8356
8357     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8358                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8359       int dummy, s=6; char buf[MSG_SIZ];
8360       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8361       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8362       if(startedFromSetupPosition) return;
8363       ParseFEN(boards[0], &dummy, message+s);
8364       DrawPosition(TRUE, boards[0]);
8365       startedFromSetupPosition = TRUE;
8366       return;
8367     }
8368     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8369      * want this, I was asked to put it in, and obliged.
8370      */
8371     if (!strncmp(message, "setboard ", 9)) {
8372         Board initial_position;
8373
8374         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8375
8376         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8377             DisplayError(_("Bad FEN received from engine"), 0);
8378             return ;
8379         } else {
8380            Reset(TRUE, FALSE);
8381            CopyBoard(boards[0], initial_position);
8382            initialRulePlies = FENrulePlies;
8383            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8384            else gameMode = MachinePlaysBlack;
8385            DrawPosition(FALSE, boards[currentMove]);
8386         }
8387         return;
8388     }
8389
8390     /*
8391      * Look for communication commands
8392      */
8393     if (!strncmp(message, "telluser ", 9)) {
8394         if(message[9] == '\\' && message[10] == '\\')
8395             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8396         PlayTellSound();
8397         DisplayNote(message + 9);
8398         return;
8399     }
8400     if (!strncmp(message, "tellusererror ", 14)) {
8401         cps->userError = 1;
8402         if(message[14] == '\\' && message[15] == '\\')
8403             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8404         PlayTellSound();
8405         DisplayError(message + 14, 0);
8406         return;
8407     }
8408     if (!strncmp(message, "tellopponent ", 13)) {
8409       if (appData.icsActive) {
8410         if (loggedOn) {
8411           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8412           SendToICS(buf1);
8413         }
8414       } else {
8415         DisplayNote(message + 13);
8416       }
8417       return;
8418     }
8419     if (!strncmp(message, "tellothers ", 11)) {
8420       if (appData.icsActive) {
8421         if (loggedOn) {
8422           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8423           SendToICS(buf1);
8424         }
8425       }
8426       return;
8427     }
8428     if (!strncmp(message, "tellall ", 8)) {
8429       if (appData.icsActive) {
8430         if (loggedOn) {
8431           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8432           SendToICS(buf1);
8433         }
8434       } else {
8435         DisplayNote(message + 8);
8436       }
8437       return;
8438     }
8439     if (strncmp(message, "warning", 7) == 0) {
8440         /* Undocumented feature, use tellusererror in new code */
8441         DisplayError(message, 0);
8442         return;
8443     }
8444     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8445         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8446         strcat(realname, " query");
8447         AskQuestion(realname, buf2, buf1, cps->pr);
8448         return;
8449     }
8450     /* Commands from the engine directly to ICS.  We don't allow these to be
8451      *  sent until we are logged on. Crafty kibitzes have been known to
8452      *  interfere with the login process.
8453      */
8454     if (loggedOn) {
8455         if (!strncmp(message, "tellics ", 8)) {
8456             SendToICS(message + 8);
8457             SendToICS("\n");
8458             return;
8459         }
8460         if (!strncmp(message, "tellicsnoalias ", 15)) {
8461             SendToICS(ics_prefix);
8462             SendToICS(message + 15);
8463             SendToICS("\n");
8464             return;
8465         }
8466         /* The following are for backward compatibility only */
8467         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8468             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8469             SendToICS(ics_prefix);
8470             SendToICS(message);
8471             SendToICS("\n");
8472             return;
8473         }
8474     }
8475     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8476         return;
8477     }
8478     /*
8479      * If the move is illegal, cancel it and redraw the board.
8480      * Also deal with other error cases.  Matching is rather loose
8481      * here to accommodate engines written before the spec.
8482      */
8483     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8484         strncmp(message, "Error", 5) == 0) {
8485         if (StrStr(message, "name") ||
8486             StrStr(message, "rating") || StrStr(message, "?") ||
8487             StrStr(message, "result") || StrStr(message, "board") ||
8488             StrStr(message, "bk") || StrStr(message, "computer") ||
8489             StrStr(message, "variant") || StrStr(message, "hint") ||
8490             StrStr(message, "random") || StrStr(message, "depth") ||
8491             StrStr(message, "accepted")) {
8492             return;
8493         }
8494         if (StrStr(message, "protover")) {
8495           /* Program is responding to input, so it's apparently done
8496              initializing, and this error message indicates it is
8497              protocol version 1.  So we don't need to wait any longer
8498              for it to initialize and send feature commands. */
8499           FeatureDone(cps, 1);
8500           cps->protocolVersion = 1;
8501           return;
8502         }
8503         cps->maybeThinking = FALSE;
8504
8505         if (StrStr(message, "draw")) {
8506             /* Program doesn't have "draw" command */
8507             cps->sendDrawOffers = 0;
8508             return;
8509         }
8510         if (cps->sendTime != 1 &&
8511             (StrStr(message, "time") || StrStr(message, "otim"))) {
8512           /* Program apparently doesn't have "time" or "otim" command */
8513           cps->sendTime = 0;
8514           return;
8515         }
8516         if (StrStr(message, "analyze")) {
8517             cps->analysisSupport = FALSE;
8518             cps->analyzing = FALSE;
8519 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8520             EditGameEvent(); // [HGM] try to preserve loaded game
8521             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8522             DisplayError(buf2, 0);
8523             return;
8524         }
8525         if (StrStr(message, "(no matching move)st")) {
8526           /* Special kludge for GNU Chess 4 only */
8527           cps->stKludge = TRUE;
8528           SendTimeControl(cps, movesPerSession, timeControl,
8529                           timeIncrement, appData.searchDepth,
8530                           searchTime);
8531           return;
8532         }
8533         if (StrStr(message, "(no matching move)sd")) {
8534           /* Special kludge for GNU Chess 4 only */
8535           cps->sdKludge = TRUE;
8536           SendTimeControl(cps, movesPerSession, timeControl,
8537                           timeIncrement, appData.searchDepth,
8538                           searchTime);
8539           return;
8540         }
8541         if (!StrStr(message, "llegal")) {
8542             return;
8543         }
8544         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8545             gameMode == IcsIdle) return;
8546         if (forwardMostMove <= backwardMostMove) return;
8547         if (pausing) PauseEvent();
8548       if(appData.forceIllegal) {
8549             // [HGM] illegal: machine refused move; force position after move into it
8550           SendToProgram("force\n", cps);
8551           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8552                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8553                 // when black is to move, while there might be nothing on a2 or black
8554                 // might already have the move. So send the board as if white has the move.
8555                 // But first we must change the stm of the engine, as it refused the last move
8556                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8557                 if(WhiteOnMove(forwardMostMove)) {
8558                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8559                     SendBoard(cps, forwardMostMove); // kludgeless board
8560                 } else {
8561                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8562                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8563                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8564                 }
8565           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8566             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8567                  gameMode == TwoMachinesPlay)
8568               SendToProgram("go\n", cps);
8569             return;
8570       } else
8571         if (gameMode == PlayFromGameFile) {
8572             /* Stop reading this game file */
8573             gameMode = EditGame;
8574             ModeHighlight();
8575         }
8576         /* [HGM] illegal-move claim should forfeit game when Xboard */
8577         /* only passes fully legal moves                            */
8578         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8579             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8580                                 "False illegal-move claim", GE_XBOARD );
8581             return; // do not take back move we tested as valid
8582         }
8583         currentMove = forwardMostMove-1;
8584         DisplayMove(currentMove-1); /* before DisplayMoveError */
8585         SwitchClocks(forwardMostMove-1); // [HGM] race
8586         DisplayBothClocks();
8587         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8588                 parseList[currentMove], _(cps->which));
8589         DisplayMoveError(buf1);
8590         DrawPosition(FALSE, boards[currentMove]);
8591
8592         SetUserThinkingEnables();
8593         return;
8594     }
8595     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8596         /* Program has a broken "time" command that
8597            outputs a string not ending in newline.
8598            Don't use it. */
8599         cps->sendTime = 0;
8600     }
8601
8602     /*
8603      * If chess program startup fails, exit with an error message.
8604      * Attempts to recover here are futile. [HGM] Well, we try anyway
8605      */
8606     if ((StrStr(message, "unknown host") != NULL)
8607         || (StrStr(message, "No remote directory") != NULL)
8608         || (StrStr(message, "not found") != NULL)
8609         || (StrStr(message, "No such file") != NULL)
8610         || (StrStr(message, "can't alloc") != NULL)
8611         || (StrStr(message, "Permission denied") != NULL)) {
8612
8613         cps->maybeThinking = FALSE;
8614         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8615                 _(cps->which), cps->program, cps->host, message);
8616         RemoveInputSource(cps->isr);
8617         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8618             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8619             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8620         }
8621         return;
8622     }
8623
8624     /*
8625      * Look for hint output
8626      */
8627     if (sscanf(message, "Hint: %s", buf1) == 1) {
8628         if (cps == &first && hintRequested) {
8629             hintRequested = FALSE;
8630             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8631                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8632                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8633                                     PosFlags(forwardMostMove),
8634                                     fromY, fromX, toY, toX, promoChar, buf1);
8635                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8636                 DisplayInformation(buf2);
8637             } else {
8638                 /* Hint move could not be parsed!? */
8639               snprintf(buf2, sizeof(buf2),
8640                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8641                         buf1, _(cps->which));
8642                 DisplayError(buf2, 0);
8643             }
8644         } else {
8645           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8646         }
8647         return;
8648     }
8649
8650     /*
8651      * Ignore other messages if game is not in progress
8652      */
8653     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8654         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8655
8656     /*
8657      * look for win, lose, draw, or draw offer
8658      */
8659     if (strncmp(message, "1-0", 3) == 0) {
8660         char *p, *q, *r = "";
8661         p = strchr(message, '{');
8662         if (p) {
8663             q = strchr(p, '}');
8664             if (q) {
8665                 *q = NULLCHAR;
8666                 r = p + 1;
8667             }
8668         }
8669         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8670         return;
8671     } else if (strncmp(message, "0-1", 3) == 0) {
8672         char *p, *q, *r = "";
8673         p = strchr(message, '{');
8674         if (p) {
8675             q = strchr(p, '}');
8676             if (q) {
8677                 *q = NULLCHAR;
8678                 r = p + 1;
8679             }
8680         }
8681         /* Kludge for Arasan 4.1 bug */
8682         if (strcmp(r, "Black resigns") == 0) {
8683             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8684             return;
8685         }
8686         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8687         return;
8688     } else if (strncmp(message, "1/2", 3) == 0) {
8689         char *p, *q, *r = "";
8690         p = strchr(message, '{');
8691         if (p) {
8692             q = strchr(p, '}');
8693             if (q) {
8694                 *q = NULLCHAR;
8695                 r = p + 1;
8696             }
8697         }
8698
8699         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8700         return;
8701
8702     } else if (strncmp(message, "White resign", 12) == 0) {
8703         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8704         return;
8705     } else if (strncmp(message, "Black resign", 12) == 0) {
8706         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8707         return;
8708     } else if (strncmp(message, "White matches", 13) == 0 ||
8709                strncmp(message, "Black matches", 13) == 0   ) {
8710         /* [HGM] ignore GNUShogi noises */
8711         return;
8712     } else if (strncmp(message, "White", 5) == 0 &&
8713                message[5] != '(' &&
8714                StrStr(message, "Black") == NULL) {
8715         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8716         return;
8717     } else if (strncmp(message, "Black", 5) == 0 &&
8718                message[5] != '(') {
8719         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8720         return;
8721     } else if (strcmp(message, "resign") == 0 ||
8722                strcmp(message, "computer resigns") == 0) {
8723         switch (gameMode) {
8724           case MachinePlaysBlack:
8725           case IcsPlayingBlack:
8726             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8727             break;
8728           case MachinePlaysWhite:
8729           case IcsPlayingWhite:
8730             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8731             break;
8732           case TwoMachinesPlay:
8733             if (cps->twoMachinesColor[0] == 'w')
8734               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8735             else
8736               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8737             break;
8738           default:
8739             /* can't happen */
8740             break;
8741         }
8742         return;
8743     } else if (strncmp(message, "opponent mates", 14) == 0) {
8744         switch (gameMode) {
8745           case MachinePlaysBlack:
8746           case IcsPlayingBlack:
8747             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8748             break;
8749           case MachinePlaysWhite:
8750           case IcsPlayingWhite:
8751             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8752             break;
8753           case TwoMachinesPlay:
8754             if (cps->twoMachinesColor[0] == 'w')
8755               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8756             else
8757               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8758             break;
8759           default:
8760             /* can't happen */
8761             break;
8762         }
8763         return;
8764     } else if (strncmp(message, "computer mates", 14) == 0) {
8765         switch (gameMode) {
8766           case MachinePlaysBlack:
8767           case IcsPlayingBlack:
8768             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8769             break;
8770           case MachinePlaysWhite:
8771           case IcsPlayingWhite:
8772             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8773             break;
8774           case TwoMachinesPlay:
8775             if (cps->twoMachinesColor[0] == 'w')
8776               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8777             else
8778               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8779             break;
8780           default:
8781             /* can't happen */
8782             break;
8783         }
8784         return;
8785     } else if (strncmp(message, "checkmate", 9) == 0) {
8786         if (WhiteOnMove(forwardMostMove)) {
8787             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8788         } else {
8789             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8790         }
8791         return;
8792     } else if (strstr(message, "Draw") != NULL ||
8793                strstr(message, "game is a draw") != NULL) {
8794         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8795         return;
8796     } else if (strstr(message, "offer") != NULL &&
8797                strstr(message, "draw") != NULL) {
8798 #if ZIPPY
8799         if (appData.zippyPlay && first.initDone) {
8800             /* Relay offer to ICS */
8801             SendToICS(ics_prefix);
8802             SendToICS("draw\n");
8803         }
8804 #endif
8805         cps->offeredDraw = 2; /* valid until this engine moves twice */
8806         if (gameMode == TwoMachinesPlay) {
8807             if (cps->other->offeredDraw) {
8808                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8809             /* [HGM] in two-machine mode we delay relaying draw offer      */
8810             /* until after we also have move, to see if it is really claim */
8811             }
8812         } else if (gameMode == MachinePlaysWhite ||
8813                    gameMode == MachinePlaysBlack) {
8814           if (userOfferedDraw) {
8815             DisplayInformation(_("Machine accepts your draw offer"));
8816             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8817           } else {
8818             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8819           }
8820         }
8821     }
8822
8823
8824     /*
8825      * Look for thinking output
8826      */
8827     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8828           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8829                                 ) {
8830         int plylev, mvleft, mvtot, curscore, time;
8831         char mvname[MOVE_LEN];
8832         u64 nodes; // [DM]
8833         char plyext;
8834         int ignore = FALSE;
8835         int prefixHint = FALSE;
8836         mvname[0] = NULLCHAR;
8837
8838         switch (gameMode) {
8839           case MachinePlaysBlack:
8840           case IcsPlayingBlack:
8841             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8842             break;
8843           case MachinePlaysWhite:
8844           case IcsPlayingWhite:
8845             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8846             break;
8847           case AnalyzeMode:
8848           case AnalyzeFile:
8849             break;
8850           case IcsObserving: /* [DM] icsEngineAnalyze */
8851             if (!appData.icsEngineAnalyze) ignore = TRUE;
8852             break;
8853           case TwoMachinesPlay:
8854             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8855                 ignore = TRUE;
8856             }
8857             break;
8858           default:
8859             ignore = TRUE;
8860             break;
8861         }
8862
8863         if (!ignore) {
8864             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8865             buf1[0] = NULLCHAR;
8866             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8867                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8868
8869                 if (plyext != ' ' && plyext != '\t') {
8870                     time *= 100;
8871                 }
8872
8873                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8874                 if( cps->scoreIsAbsolute &&
8875                     ( gameMode == MachinePlaysBlack ||
8876                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8877                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8878                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8879                      !WhiteOnMove(currentMove)
8880                     ) )
8881                 {
8882                     curscore = -curscore;
8883                 }
8884
8885                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8886
8887                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8888                         char buf[MSG_SIZ];
8889                         FILE *f;
8890                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8891                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8892                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8893                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8894                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8895                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8896                                 fclose(f);
8897                         } else DisplayError(_("failed writing PV"), 0);
8898                 }
8899
8900                 tempStats.depth = plylev;
8901                 tempStats.nodes = nodes;
8902                 tempStats.time = time;
8903                 tempStats.score = curscore;
8904                 tempStats.got_only_move = 0;
8905
8906                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8907                         int ticklen;
8908
8909                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8910                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8911                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8912                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8913                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8914                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8915                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8916                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8917                 }
8918
8919                 /* Buffer overflow protection */
8920                 if (pv[0] != NULLCHAR) {
8921                     if (strlen(pv) >= sizeof(tempStats.movelist)
8922                         && appData.debugMode) {
8923                         fprintf(debugFP,
8924                                 "PV is too long; using the first %u bytes.\n",
8925                                 (unsigned) sizeof(tempStats.movelist) - 1);
8926                     }
8927
8928                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8929                 } else {
8930                     sprintf(tempStats.movelist, " no PV\n");
8931                 }
8932
8933                 if (tempStats.seen_stat) {
8934                     tempStats.ok_to_send = 1;
8935                 }
8936
8937                 if (strchr(tempStats.movelist, '(') != NULL) {
8938                     tempStats.line_is_book = 1;
8939                     tempStats.nr_moves = 0;
8940                     tempStats.moves_left = 0;
8941                 } else {
8942                     tempStats.line_is_book = 0;
8943                 }
8944
8945                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8946                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8947
8948                 SendProgramStatsToFrontend( cps, &tempStats );
8949
8950                 /*
8951                     [AS] Protect the thinkOutput buffer from overflow... this
8952                     is only useful if buf1 hasn't overflowed first!
8953                 */
8954                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8955                          plylev,
8956                          (gameMode == TwoMachinesPlay ?
8957                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8958                          ((double) curscore) / 100.0,
8959                          prefixHint ? lastHint : "",
8960                          prefixHint ? " " : "" );
8961
8962                 if( buf1[0] != NULLCHAR ) {
8963                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8964
8965                     if( strlen(pv) > max_len ) {
8966                         if( appData.debugMode) {
8967                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8968                         }
8969                         pv[max_len+1] = '\0';
8970                     }
8971
8972                     strcat( thinkOutput, pv);
8973                 }
8974
8975                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8976                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8977                     DisplayMove(currentMove - 1);
8978                 }
8979                 return;
8980
8981             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8982                 /* crafty (9.25+) says "(only move) <move>"
8983                  * if there is only 1 legal move
8984                  */
8985                 sscanf(p, "(only move) %s", buf1);
8986                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8987                 sprintf(programStats.movelist, "%s (only move)", buf1);
8988                 programStats.depth = 1;
8989                 programStats.nr_moves = 1;
8990                 programStats.moves_left = 1;
8991                 programStats.nodes = 1;
8992                 programStats.time = 1;
8993                 programStats.got_only_move = 1;
8994
8995                 /* Not really, but we also use this member to
8996                    mean "line isn't going to change" (Crafty
8997                    isn't searching, so stats won't change) */
8998                 programStats.line_is_book = 1;
8999
9000                 SendProgramStatsToFrontend( cps, &programStats );
9001
9002                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9003                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9004                     DisplayMove(currentMove - 1);
9005                 }
9006                 return;
9007             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9008                               &time, &nodes, &plylev, &mvleft,
9009                               &mvtot, mvname) >= 5) {
9010                 /* The stat01: line is from Crafty (9.29+) in response
9011                    to the "." command */
9012                 programStats.seen_stat = 1;
9013                 cps->maybeThinking = TRUE;
9014
9015                 if (programStats.got_only_move || !appData.periodicUpdates)
9016                   return;
9017
9018                 programStats.depth = plylev;
9019                 programStats.time = time;
9020                 programStats.nodes = nodes;
9021                 programStats.moves_left = mvleft;
9022                 programStats.nr_moves = mvtot;
9023                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9024                 programStats.ok_to_send = 1;
9025                 programStats.movelist[0] = '\0';
9026
9027                 SendProgramStatsToFrontend( cps, &programStats );
9028
9029                 return;
9030
9031             } else if (strncmp(message,"++",2) == 0) {
9032                 /* Crafty 9.29+ outputs this */
9033                 programStats.got_fail = 2;
9034                 return;
9035
9036             } else if (strncmp(message,"--",2) == 0) {
9037                 /* Crafty 9.29+ outputs this */
9038                 programStats.got_fail = 1;
9039                 return;
9040
9041             } else if (thinkOutput[0] != NULLCHAR &&
9042                        strncmp(message, "    ", 4) == 0) {
9043                 unsigned message_len;
9044
9045                 p = message;
9046                 while (*p && *p == ' ') p++;
9047
9048                 message_len = strlen( p );
9049
9050                 /* [AS] Avoid buffer overflow */
9051                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9052                     strcat(thinkOutput, " ");
9053                     strcat(thinkOutput, p);
9054                 }
9055
9056                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9057                     strcat(programStats.movelist, " ");
9058                     strcat(programStats.movelist, p);
9059                 }
9060
9061                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9062                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9063                     DisplayMove(currentMove - 1);
9064                 }
9065                 return;
9066             }
9067         }
9068         else {
9069             buf1[0] = NULLCHAR;
9070
9071             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9072                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9073             {
9074                 ChessProgramStats cpstats;
9075
9076                 if (plyext != ' ' && plyext != '\t') {
9077                     time *= 100;
9078                 }
9079
9080                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9081                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9082                     curscore = -curscore;
9083                 }
9084
9085                 cpstats.depth = plylev;
9086                 cpstats.nodes = nodes;
9087                 cpstats.time = time;
9088                 cpstats.score = curscore;
9089                 cpstats.got_only_move = 0;
9090                 cpstats.movelist[0] = '\0';
9091
9092                 if (buf1[0] != NULLCHAR) {
9093                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9094                 }
9095
9096                 cpstats.ok_to_send = 0;
9097                 cpstats.line_is_book = 0;
9098                 cpstats.nr_moves = 0;
9099                 cpstats.moves_left = 0;
9100
9101                 SendProgramStatsToFrontend( cps, &cpstats );
9102             }
9103         }
9104     }
9105 }
9106
9107
9108 /* Parse a game score from the character string "game", and
9109    record it as the history of the current game.  The game
9110    score is NOT assumed to start from the standard position.
9111    The display is not updated in any way.
9112    */
9113 void
9114 ParseGameHistory (char *game)
9115 {
9116     ChessMove moveType;
9117     int fromX, fromY, toX, toY, boardIndex;
9118     char promoChar;
9119     char *p, *q;
9120     char buf[MSG_SIZ];
9121
9122     if (appData.debugMode)
9123       fprintf(debugFP, "Parsing game history: %s\n", game);
9124
9125     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9126     gameInfo.site = StrSave(appData.icsHost);
9127     gameInfo.date = PGNDate();
9128     gameInfo.round = StrSave("-");
9129
9130     /* Parse out names of players */
9131     while (*game == ' ') game++;
9132     p = buf;
9133     while (*game != ' ') *p++ = *game++;
9134     *p = NULLCHAR;
9135     gameInfo.white = StrSave(buf);
9136     while (*game == ' ') game++;
9137     p = buf;
9138     while (*game != ' ' && *game != '\n') *p++ = *game++;
9139     *p = NULLCHAR;
9140     gameInfo.black = StrSave(buf);
9141
9142     /* Parse moves */
9143     boardIndex = blackPlaysFirst ? 1 : 0;
9144     yynewstr(game);
9145     for (;;) {
9146         yyboardindex = boardIndex;
9147         moveType = (ChessMove) Myylex();
9148         switch (moveType) {
9149           case IllegalMove:             /* maybe suicide chess, etc. */
9150   if (appData.debugMode) {
9151     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9152     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9153     setbuf(debugFP, NULL);
9154   }
9155           case WhitePromotion:
9156           case BlackPromotion:
9157           case WhiteNonPromotion:
9158           case BlackNonPromotion:
9159           case NormalMove:
9160           case WhiteCapturesEnPassant:
9161           case BlackCapturesEnPassant:
9162           case WhiteKingSideCastle:
9163           case WhiteQueenSideCastle:
9164           case BlackKingSideCastle:
9165           case BlackQueenSideCastle:
9166           case WhiteKingSideCastleWild:
9167           case WhiteQueenSideCastleWild:
9168           case BlackKingSideCastleWild:
9169           case BlackQueenSideCastleWild:
9170           /* PUSH Fabien */
9171           case WhiteHSideCastleFR:
9172           case WhiteASideCastleFR:
9173           case BlackHSideCastleFR:
9174           case BlackASideCastleFR:
9175           /* POP Fabien */
9176             fromX = currentMoveString[0] - AAA;
9177             fromY = currentMoveString[1] - ONE;
9178             toX = currentMoveString[2] - AAA;
9179             toY = currentMoveString[3] - ONE;
9180             promoChar = currentMoveString[4];
9181             break;
9182           case WhiteDrop:
9183           case BlackDrop:
9184             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9185             fromX = moveType == WhiteDrop ?
9186               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9187             (int) CharToPiece(ToLower(currentMoveString[0]));
9188             fromY = DROP_RANK;
9189             toX = currentMoveString[2] - AAA;
9190             toY = currentMoveString[3] - ONE;
9191             promoChar = NULLCHAR;
9192             break;
9193           case AmbiguousMove:
9194             /* bug? */
9195             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9196   if (appData.debugMode) {
9197     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9198     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9199     setbuf(debugFP, NULL);
9200   }
9201             DisplayError(buf, 0);
9202             return;
9203           case ImpossibleMove:
9204             /* bug? */
9205             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9206   if (appData.debugMode) {
9207     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9208     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9209     setbuf(debugFP, NULL);
9210   }
9211             DisplayError(buf, 0);
9212             return;
9213           case EndOfFile:
9214             if (boardIndex < backwardMostMove) {
9215                 /* Oops, gap.  How did that happen? */
9216                 DisplayError(_("Gap in move list"), 0);
9217                 return;
9218             }
9219             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9220             if (boardIndex > forwardMostMove) {
9221                 forwardMostMove = boardIndex;
9222             }
9223             return;
9224           case ElapsedTime:
9225             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9226                 strcat(parseList[boardIndex-1], " ");
9227                 strcat(parseList[boardIndex-1], yy_text);
9228             }
9229             continue;
9230           case Comment:
9231           case PGNTag:
9232           case NAG:
9233           default:
9234             /* ignore */
9235             continue;
9236           case WhiteWins:
9237           case BlackWins:
9238           case GameIsDrawn:
9239           case GameUnfinished:
9240             if (gameMode == IcsExamining) {
9241                 if (boardIndex < backwardMostMove) {
9242                     /* Oops, gap.  How did that happen? */
9243                     return;
9244                 }
9245                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9246                 return;
9247             }
9248             gameInfo.result = moveType;
9249             p = strchr(yy_text, '{');
9250             if (p == NULL) p = strchr(yy_text, '(');
9251             if (p == NULL) {
9252                 p = yy_text;
9253                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9254             } else {
9255                 q = strchr(p, *p == '{' ? '}' : ')');
9256                 if (q != NULL) *q = NULLCHAR;
9257                 p++;
9258             }
9259             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9260             gameInfo.resultDetails = StrSave(p);
9261             continue;
9262         }
9263         if (boardIndex >= forwardMostMove &&
9264             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9265             backwardMostMove = blackPlaysFirst ? 1 : 0;
9266             return;
9267         }
9268         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9269                                  fromY, fromX, toY, toX, promoChar,
9270                                  parseList[boardIndex]);
9271         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9272         /* currentMoveString is set as a side-effect of yylex */
9273         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9274         strcat(moveList[boardIndex], "\n");
9275         boardIndex++;
9276         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9277         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9278           case MT_NONE:
9279           case MT_STALEMATE:
9280           default:
9281             break;
9282           case MT_CHECK:
9283             if(gameInfo.variant != VariantShogi)
9284                 strcat(parseList[boardIndex - 1], "+");
9285             break;
9286           case MT_CHECKMATE:
9287           case MT_STAINMATE:
9288             strcat(parseList[boardIndex - 1], "#");
9289             break;
9290         }
9291     }
9292 }
9293
9294
9295 /* Apply a move to the given board  */
9296 void
9297 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9298 {
9299   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9300   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9301
9302     /* [HGM] compute & store e.p. status and castling rights for new position */
9303     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9304
9305       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9306       oldEP = (signed char)board[EP_STATUS];
9307       board[EP_STATUS] = EP_NONE;
9308
9309   if (fromY == DROP_RANK) {
9310         /* must be first */
9311         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9312             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9313             return;
9314         }
9315         piece = board[toY][toX] = (ChessSquare) fromX;
9316   } else {
9317       int i;
9318
9319       if( board[toY][toX] != EmptySquare )
9320            board[EP_STATUS] = EP_CAPTURE;
9321
9322       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9323            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9324                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9325       } else
9326       if( board[fromY][fromX] == WhitePawn ) {
9327            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9328                board[EP_STATUS] = EP_PAWN_MOVE;
9329            if( toY-fromY==2) {
9330                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9331                         gameInfo.variant != VariantBerolina || toX < fromX)
9332                       board[EP_STATUS] = toX | berolina;
9333                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9334                         gameInfo.variant != VariantBerolina || toX > fromX)
9335                       board[EP_STATUS] = toX;
9336            }
9337       } else
9338       if( board[fromY][fromX] == BlackPawn ) {
9339            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9340                board[EP_STATUS] = EP_PAWN_MOVE;
9341            if( toY-fromY== -2) {
9342                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9343                         gameInfo.variant != VariantBerolina || toX < fromX)
9344                       board[EP_STATUS] = toX | berolina;
9345                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9346                         gameInfo.variant != VariantBerolina || toX > fromX)
9347                       board[EP_STATUS] = toX;
9348            }
9349        }
9350
9351        for(i=0; i<nrCastlingRights; i++) {
9352            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9353               board[CASTLING][i] == toX   && castlingRank[i] == toY
9354              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9355        }
9356
9357        if(gameInfo.variant == VariantSChess) { // update virginity
9358            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9359            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9360            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9361            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9362        }
9363
9364      if (fromX == toX && fromY == toY) return;
9365
9366      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9367      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9368      if(gameInfo.variant == VariantKnightmate)
9369          king += (int) WhiteUnicorn - (int) WhiteKing;
9370
9371     /* Code added by Tord: */
9372     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9373     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9374         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9375       board[fromY][fromX] = EmptySquare;
9376       board[toY][toX] = EmptySquare;
9377       if((toX > fromX) != (piece == WhiteRook)) {
9378         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9379       } else {
9380         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9381       }
9382     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9383                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9384       board[fromY][fromX] = EmptySquare;
9385       board[toY][toX] = EmptySquare;
9386       if((toX > fromX) != (piece == BlackRook)) {
9387         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9388       } else {
9389         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9390       }
9391     /* End of code added by Tord */
9392
9393     } else if (board[fromY][fromX] == king
9394         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9395         && toY == fromY && toX > fromX+1) {
9396         board[fromY][fromX] = EmptySquare;
9397         board[toY][toX] = king;
9398         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9399         board[fromY][BOARD_RGHT-1] = EmptySquare;
9400     } else if (board[fromY][fromX] == king
9401         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9402                && toY == fromY && toX < fromX-1) {
9403         board[fromY][fromX] = EmptySquare;
9404         board[toY][toX] = king;
9405         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9406         board[fromY][BOARD_LEFT] = EmptySquare;
9407     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9408                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9409                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9410                ) {
9411         /* white pawn promotion */
9412         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9413         if(gameInfo.variant==VariantBughouse ||
9414            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9415             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9416         board[fromY][fromX] = EmptySquare;
9417     } else if ((fromY >= BOARD_HEIGHT>>1)
9418                && (toX != fromX)
9419                && gameInfo.variant != VariantXiangqi
9420                && gameInfo.variant != VariantBerolina
9421                && (board[fromY][fromX] == WhitePawn)
9422                && (board[toY][toX] == EmptySquare)) {
9423         board[fromY][fromX] = EmptySquare;
9424         board[toY][toX] = WhitePawn;
9425         captured = board[toY - 1][toX];
9426         board[toY - 1][toX] = EmptySquare;
9427     } else if ((fromY == BOARD_HEIGHT-4)
9428                && (toX == fromX)
9429                && gameInfo.variant == VariantBerolina
9430                && (board[fromY][fromX] == WhitePawn)
9431                && (board[toY][toX] == EmptySquare)) {
9432         board[fromY][fromX] = EmptySquare;
9433         board[toY][toX] = WhitePawn;
9434         if(oldEP & EP_BEROLIN_A) {
9435                 captured = board[fromY][fromX-1];
9436                 board[fromY][fromX-1] = EmptySquare;
9437         }else{  captured = board[fromY][fromX+1];
9438                 board[fromY][fromX+1] = EmptySquare;
9439         }
9440     } else if (board[fromY][fromX] == king
9441         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9442                && toY == fromY && toX > fromX+1) {
9443         board[fromY][fromX] = EmptySquare;
9444         board[toY][toX] = king;
9445         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9446         board[fromY][BOARD_RGHT-1] = EmptySquare;
9447     } else if (board[fromY][fromX] == king
9448         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9449                && toY == fromY && toX < fromX-1) {
9450         board[fromY][fromX] = EmptySquare;
9451         board[toY][toX] = king;
9452         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9453         board[fromY][BOARD_LEFT] = EmptySquare;
9454     } else if (fromY == 7 && fromX == 3
9455                && board[fromY][fromX] == BlackKing
9456                && toY == 7 && toX == 5) {
9457         board[fromY][fromX] = EmptySquare;
9458         board[toY][toX] = BlackKing;
9459         board[fromY][7] = EmptySquare;
9460         board[toY][4] = BlackRook;
9461     } else if (fromY == 7 && fromX == 3
9462                && board[fromY][fromX] == BlackKing
9463                && toY == 7 && toX == 1) {
9464         board[fromY][fromX] = EmptySquare;
9465         board[toY][toX] = BlackKing;
9466         board[fromY][0] = EmptySquare;
9467         board[toY][2] = BlackRook;
9468     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9469                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9470                && toY < promoRank && promoChar
9471                ) {
9472         /* black pawn promotion */
9473         board[toY][toX] = CharToPiece(ToLower(promoChar));
9474         if(gameInfo.variant==VariantBughouse ||
9475            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9476             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9477         board[fromY][fromX] = EmptySquare;
9478     } else if ((fromY < BOARD_HEIGHT>>1)
9479                && (toX != fromX)
9480                && gameInfo.variant != VariantXiangqi
9481                && gameInfo.variant != VariantBerolina
9482                && (board[fromY][fromX] == BlackPawn)
9483                && (board[toY][toX] == EmptySquare)) {
9484         board[fromY][fromX] = EmptySquare;
9485         board[toY][toX] = BlackPawn;
9486         captured = board[toY + 1][toX];
9487         board[toY + 1][toX] = EmptySquare;
9488     } else if ((fromY == 3)
9489                && (toX == fromX)
9490                && gameInfo.variant == VariantBerolina
9491                && (board[fromY][fromX] == BlackPawn)
9492                && (board[toY][toX] == EmptySquare)) {
9493         board[fromY][fromX] = EmptySquare;
9494         board[toY][toX] = BlackPawn;
9495         if(oldEP & EP_BEROLIN_A) {
9496                 captured = board[fromY][fromX-1];
9497                 board[fromY][fromX-1] = EmptySquare;
9498         }else{  captured = board[fromY][fromX+1];
9499                 board[fromY][fromX+1] = EmptySquare;
9500         }
9501     } else {
9502         board[toY][toX] = board[fromY][fromX];
9503         board[fromY][fromX] = EmptySquare;
9504     }
9505   }
9506
9507     if (gameInfo.holdingsWidth != 0) {
9508
9509       /* !!A lot more code needs to be written to support holdings  */
9510       /* [HGM] OK, so I have written it. Holdings are stored in the */
9511       /* penultimate board files, so they are automaticlly stored   */
9512       /* in the game history.                                       */
9513       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9514                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9515         /* Delete from holdings, by decreasing count */
9516         /* and erasing image if necessary            */
9517         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9518         if(p < (int) BlackPawn) { /* white drop */
9519              p -= (int)WhitePawn;
9520                  p = PieceToNumber((ChessSquare)p);
9521              if(p >= gameInfo.holdingsSize) p = 0;
9522              if(--board[p][BOARD_WIDTH-2] <= 0)
9523                   board[p][BOARD_WIDTH-1] = EmptySquare;
9524              if((int)board[p][BOARD_WIDTH-2] < 0)
9525                         board[p][BOARD_WIDTH-2] = 0;
9526         } else {                  /* black drop */
9527              p -= (int)BlackPawn;
9528                  p = PieceToNumber((ChessSquare)p);
9529              if(p >= gameInfo.holdingsSize) p = 0;
9530              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9531                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9532              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9533                         board[BOARD_HEIGHT-1-p][1] = 0;
9534         }
9535       }
9536       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9537           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9538         /* [HGM] holdings: Add to holdings, if holdings exist */
9539         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9540                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9541                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9542         }
9543         p = (int) captured;
9544         if (p >= (int) BlackPawn) {
9545           p -= (int)BlackPawn;
9546           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9547                   /* in Shogi restore piece to its original  first */
9548                   captured = (ChessSquare) (DEMOTED captured);
9549                   p = DEMOTED p;
9550           }
9551           p = PieceToNumber((ChessSquare)p);
9552           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9553           board[p][BOARD_WIDTH-2]++;
9554           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9555         } else {
9556           p -= (int)WhitePawn;
9557           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9558                   captured = (ChessSquare) (DEMOTED captured);
9559                   p = DEMOTED p;
9560           }
9561           p = PieceToNumber((ChessSquare)p);
9562           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9563           board[BOARD_HEIGHT-1-p][1]++;
9564           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9565         }
9566       }
9567     } else if (gameInfo.variant == VariantAtomic) {
9568       if (captured != EmptySquare) {
9569         int y, x;
9570         for (y = toY-1; y <= toY+1; y++) {
9571           for (x = toX-1; x <= toX+1; x++) {
9572             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9573                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9574               board[y][x] = EmptySquare;
9575             }
9576           }
9577         }
9578         board[toY][toX] = EmptySquare;
9579       }
9580     }
9581     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9582         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9583     } else
9584     if(promoChar == '+') {
9585         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9586         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9587     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9588         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9589         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9590            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9591         board[toY][toX] = newPiece;
9592     }
9593     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9594                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9595         // [HGM] superchess: take promotion piece out of holdings
9596         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9597         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9598             if(!--board[k][BOARD_WIDTH-2])
9599                 board[k][BOARD_WIDTH-1] = EmptySquare;
9600         } else {
9601             if(!--board[BOARD_HEIGHT-1-k][1])
9602                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9603         }
9604     }
9605
9606 }
9607
9608 /* Updates forwardMostMove */
9609 void
9610 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9611 {
9612 //    forwardMostMove++; // [HGM] bare: moved downstream
9613
9614     (void) CoordsToAlgebraic(boards[forwardMostMove],
9615                              PosFlags(forwardMostMove),
9616                              fromY, fromX, toY, toX, promoChar,
9617                              parseList[forwardMostMove]);
9618
9619     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9620         int timeLeft; static int lastLoadFlag=0; int king, piece;
9621         piece = boards[forwardMostMove][fromY][fromX];
9622         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9623         if(gameInfo.variant == VariantKnightmate)
9624             king += (int) WhiteUnicorn - (int) WhiteKing;
9625         if(forwardMostMove == 0) {
9626             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9627                 fprintf(serverMoves, "%s;", UserName());
9628             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9629                 fprintf(serverMoves, "%s;", second.tidy);
9630             fprintf(serverMoves, "%s;", first.tidy);
9631             if(gameMode == MachinePlaysWhite)
9632                 fprintf(serverMoves, "%s;", UserName());
9633             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9634                 fprintf(serverMoves, "%s;", second.tidy);
9635         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9636         lastLoadFlag = loadFlag;
9637         // print base move
9638         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9639         // print castling suffix
9640         if( toY == fromY && piece == king ) {
9641             if(toX-fromX > 1)
9642                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9643             if(fromX-toX >1)
9644                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9645         }
9646         // e.p. suffix
9647         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9648              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9649              boards[forwardMostMove][toY][toX] == EmptySquare
9650              && fromX != toX && fromY != toY)
9651                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9652         // promotion suffix
9653         if(promoChar != NULLCHAR) {
9654             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9655                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9656                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9657             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9658         }
9659         if(!loadFlag) {
9660                 char buf[MOVE_LEN*2], *p; int len;
9661             fprintf(serverMoves, "/%d/%d",
9662                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9663             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9664             else                      timeLeft = blackTimeRemaining/1000;
9665             fprintf(serverMoves, "/%d", timeLeft);
9666                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9667                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9668                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9669                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9670             fprintf(serverMoves, "/%s", buf);
9671         }
9672         fflush(serverMoves);
9673     }
9674
9675     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9676         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9677       return;
9678     }
9679     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9680     if (commentList[forwardMostMove+1] != NULL) {
9681         free(commentList[forwardMostMove+1]);
9682         commentList[forwardMostMove+1] = NULL;
9683     }
9684     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9685     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9686     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9687     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9688     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9689     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9690     adjustedClock = FALSE;
9691     gameInfo.result = GameUnfinished;
9692     if (gameInfo.resultDetails != NULL) {
9693         free(gameInfo.resultDetails);
9694         gameInfo.resultDetails = NULL;
9695     }
9696     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9697                               moveList[forwardMostMove - 1]);
9698     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9699       case MT_NONE:
9700       case MT_STALEMATE:
9701       default:
9702         break;
9703       case MT_CHECK:
9704         if(gameInfo.variant != VariantShogi)
9705             strcat(parseList[forwardMostMove - 1], "+");
9706         break;
9707       case MT_CHECKMATE:
9708       case MT_STAINMATE:
9709         strcat(parseList[forwardMostMove - 1], "#");
9710         break;
9711     }
9712
9713 }
9714
9715 /* Updates currentMove if not pausing */
9716 void
9717 ShowMove (int fromX, int fromY, int toX, int toY)
9718 {
9719     int instant = (gameMode == PlayFromGameFile) ?
9720         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9721     if(appData.noGUI) return;
9722     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9723         if (!instant) {
9724             if (forwardMostMove == currentMove + 1) {
9725                 AnimateMove(boards[forwardMostMove - 1],
9726                             fromX, fromY, toX, toY);
9727             }
9728         }
9729         currentMove = forwardMostMove;
9730     }
9731
9732     if (instant) return;
9733
9734     DisplayMove(currentMove - 1);
9735     DrawPosition(FALSE, boards[currentMove]);
9736     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9737             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
9738                 SetHighlights(fromX, fromY, toX, toY);
9739             }
9740     }
9741     DisplayBothClocks();
9742     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9743 }
9744
9745 void
9746 SendEgtPath (ChessProgramState *cps)
9747 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9748         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9749
9750         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9751
9752         while(*p) {
9753             char c, *q = name+1, *r, *s;
9754
9755             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9756             while(*p && *p != ',') *q++ = *p++;
9757             *q++ = ':'; *q = 0;
9758             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9759                 strcmp(name, ",nalimov:") == 0 ) {
9760                 // take nalimov path from the menu-changeable option first, if it is defined
9761               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9762                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9763             } else
9764             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9765                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9766                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9767                 s = r = StrStr(s, ":") + 1; // beginning of path info
9768                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9769                 c = *r; *r = 0;             // temporarily null-terminate path info
9770                     *--q = 0;               // strip of trailig ':' from name
9771                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9772                 *r = c;
9773                 SendToProgram(buf,cps);     // send egtbpath command for this format
9774             }
9775             if(*p == ',') p++; // read away comma to position for next format name
9776         }
9777 }
9778
9779 void
9780 InitChessProgram (ChessProgramState *cps, int setup)
9781 /* setup needed to setup FRC opening position */
9782 {
9783     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9784     if (appData.noChessProgram) return;
9785     hintRequested = FALSE;
9786     bookRequested = FALSE;
9787
9788     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9789     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9790     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9791     if(cps->memSize) { /* [HGM] memory */
9792       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9793         SendToProgram(buf, cps);
9794     }
9795     SendEgtPath(cps); /* [HGM] EGT */
9796     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9797       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9798         SendToProgram(buf, cps);
9799     }
9800
9801     SendToProgram(cps->initString, cps);
9802     if (gameInfo.variant != VariantNormal &&
9803         gameInfo.variant != VariantLoadable
9804         /* [HGM] also send variant if board size non-standard */
9805         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9806                                             ) {
9807       char *v = VariantName(gameInfo.variant);
9808       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9809         /* [HGM] in protocol 1 we have to assume all variants valid */
9810         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9811         DisplayFatalError(buf, 0, 1);
9812         return;
9813       }
9814
9815       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9816       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9817       if( gameInfo.variant == VariantXiangqi )
9818            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9819       if( gameInfo.variant == VariantShogi )
9820            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9821       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9822            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9823       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9824           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9825            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9826       if( gameInfo.variant == VariantCourier )
9827            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9828       if( gameInfo.variant == VariantSuper )
9829            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9830       if( gameInfo.variant == VariantGreat )
9831            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9832       if( gameInfo.variant == VariantSChess )
9833            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9834       if( gameInfo.variant == VariantGrand )
9835            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9836
9837       if(overruled) {
9838         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9839                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9840            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9841            if(StrStr(cps->variants, b) == NULL) {
9842                // specific sized variant not known, check if general sizing allowed
9843                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9844                    if(StrStr(cps->variants, "boardsize") == NULL) {
9845                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9846                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9847                        DisplayFatalError(buf, 0, 1);
9848                        return;
9849                    }
9850                    /* [HGM] here we really should compare with the maximum supported board size */
9851                }
9852            }
9853       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9854       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9855       SendToProgram(buf, cps);
9856     }
9857     currentlyInitializedVariant = gameInfo.variant;
9858
9859     /* [HGM] send opening position in FRC to first engine */
9860     if(setup) {
9861           SendToProgram("force\n", cps);
9862           SendBoard(cps, 0);
9863           /* engine is now in force mode! Set flag to wake it up after first move. */
9864           setboardSpoiledMachineBlack = 1;
9865     }
9866
9867     if (cps->sendICS) {
9868       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9869       SendToProgram(buf, cps);
9870     }
9871     cps->maybeThinking = FALSE;
9872     cps->offeredDraw = 0;
9873     if (!appData.icsActive) {
9874         SendTimeControl(cps, movesPerSession, timeControl,
9875                         timeIncrement, appData.searchDepth,
9876                         searchTime);
9877     }
9878     if (appData.showThinking
9879         // [HGM] thinking: four options require thinking output to be sent
9880         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9881                                 ) {
9882         SendToProgram("post\n", cps);
9883     }
9884     SendToProgram("hard\n", cps);
9885     if (!appData.ponderNextMove) {
9886         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9887            it without being sure what state we are in first.  "hard"
9888            is not a toggle, so that one is OK.
9889          */
9890         SendToProgram("easy\n", cps);
9891     }
9892     if (cps->usePing) {
9893       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9894       SendToProgram(buf, cps);
9895     }
9896     cps->initDone = TRUE;
9897     ClearEngineOutputPane(cps == &second);
9898 }
9899
9900
9901 void
9902 StartChessProgram (ChessProgramState *cps)
9903 {
9904     char buf[MSG_SIZ];
9905     int err;
9906
9907     if (appData.noChessProgram) return;
9908     cps->initDone = FALSE;
9909
9910     if (strcmp(cps->host, "localhost") == 0) {
9911         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9912     } else if (*appData.remoteShell == NULLCHAR) {
9913         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9914     } else {
9915         if (*appData.remoteUser == NULLCHAR) {
9916           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9917                     cps->program);
9918         } else {
9919           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9920                     cps->host, appData.remoteUser, cps->program);
9921         }
9922         err = StartChildProcess(buf, "", &cps->pr);
9923     }
9924
9925     if (err != 0) {
9926       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9927         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9928         if(cps != &first) return;
9929         appData.noChessProgram = TRUE;
9930         ThawUI();
9931         SetNCPMode();
9932 //      DisplayFatalError(buf, err, 1);
9933 //      cps->pr = NoProc;
9934 //      cps->isr = NULL;
9935         return;
9936     }
9937
9938     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9939     if (cps->protocolVersion > 1) {
9940       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9941       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9942       cps->comboCnt = 0;  //                and values of combo boxes
9943       SendToProgram(buf, cps);
9944     } else {
9945       SendToProgram("xboard\n", cps);
9946     }
9947 }
9948
9949 void
9950 TwoMachinesEventIfReady P((void))
9951 {
9952   static int curMess = 0;
9953   if (first.lastPing != first.lastPong) {
9954     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9955     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9956     return;
9957   }
9958   if (second.lastPing != second.lastPong) {
9959     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9960     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9961     return;
9962   }
9963   DisplayMessage("", ""); curMess = 0;
9964   ThawUI();
9965   TwoMachinesEvent();
9966 }
9967
9968 char *
9969 MakeName (char *template)
9970 {
9971     time_t clock;
9972     struct tm *tm;
9973     static char buf[MSG_SIZ];
9974     char *p = buf;
9975     int i;
9976
9977     clock = time((time_t *)NULL);
9978     tm = localtime(&clock);
9979
9980     while(*p++ = *template++) if(p[-1] == '%') {
9981         switch(*template++) {
9982           case 0:   *p = 0; return buf;
9983           case 'Y': i = tm->tm_year+1900; break;
9984           case 'y': i = tm->tm_year-100; break;
9985           case 'M': i = tm->tm_mon+1; break;
9986           case 'd': i = tm->tm_mday; break;
9987           case 'h': i = tm->tm_hour; break;
9988           case 'm': i = tm->tm_min; break;
9989           case 's': i = tm->tm_sec; break;
9990           default:  i = 0;
9991         }
9992         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9993     }
9994     return buf;
9995 }
9996
9997 int
9998 CountPlayers (char *p)
9999 {
10000     int n = 0;
10001     while(p = strchr(p, '\n')) p++, n++; // count participants
10002     return n;
10003 }
10004
10005 FILE *
10006 WriteTourneyFile (char *results, FILE *f)
10007 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10008     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10009     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10010         // create a file with tournament description
10011         fprintf(f, "-participants {%s}\n", appData.participants);
10012         fprintf(f, "-seedBase %d\n", appData.seedBase);
10013         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10014         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10015         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10016         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10017         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10018         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10019         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10020         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10021         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10022         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10023         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10024         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10025         if(searchTime > 0)
10026                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10027         else {
10028                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10029                 fprintf(f, "-tc %s\n", appData.timeControl);
10030                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10031         }
10032         fprintf(f, "-results \"%s\"\n", results);
10033     }
10034     return f;
10035 }
10036
10037 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10038
10039 void
10040 Substitute (char *participants, int expunge)
10041 {
10042     int i, changed, changes=0, nPlayers=0;
10043     char *p, *q, *r, buf[MSG_SIZ];
10044     if(participants == NULL) return;
10045     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10046     r = p = participants; q = appData.participants;
10047     while(*p && *p == *q) {
10048         if(*p == '\n') r = p+1, nPlayers++;
10049         p++; q++;
10050     }
10051     if(*p) { // difference
10052         while(*p && *p++ != '\n');
10053         while(*q && *q++ != '\n');
10054       changed = nPlayers;
10055         changes = 1 + (strcmp(p, q) != 0);
10056     }
10057     if(changes == 1) { // a single engine mnemonic was changed
10058         q = r; while(*q) nPlayers += (*q++ == '\n');
10059         p = buf; while(*r && (*p = *r++) != '\n') p++;
10060         *p = NULLCHAR;
10061         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10062         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10063         if(mnemonic[i]) { // The substitute is valid
10064             FILE *f;
10065             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10066                 flock(fileno(f), LOCK_EX);
10067                 ParseArgsFromFile(f);
10068                 fseek(f, 0, SEEK_SET);
10069                 FREE(appData.participants); appData.participants = participants;
10070                 if(expunge) { // erase results of replaced engine
10071                     int len = strlen(appData.results), w, b, dummy;
10072                     for(i=0; i<len; i++) {
10073                         Pairing(i, nPlayers, &w, &b, &dummy);
10074                         if((w == changed || b == changed) && appData.results[i] == '*') {
10075                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10076                             fclose(f);
10077                             return;
10078                         }
10079                     }
10080                     for(i=0; i<len; i++) {
10081                         Pairing(i, nPlayers, &w, &b, &dummy);
10082                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10083                     }
10084                 }
10085                 WriteTourneyFile(appData.results, f);
10086                 fclose(f); // release lock
10087                 return;
10088             }
10089         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10090     }
10091     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10092     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10093     free(participants);
10094     return;
10095 }
10096
10097 int
10098 CheckPlayers (char *participants)
10099 {
10100         int i;
10101         char buf[MSG_SIZ], *p;
10102         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10103         while(p = strchr(participants, '\n')) {
10104             *p = NULLCHAR;
10105             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10106             if(!mnemonic[i]) {
10107                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10108                 *p = '\n';
10109                 DisplayError(buf, 0);
10110                 return 1;
10111             }
10112             *p = '\n';
10113             participants = p + 1;
10114         }
10115         return 0;
10116 }
10117
10118 int
10119 CreateTourney (char *name)
10120 {
10121         FILE *f;
10122         if(matchMode && strcmp(name, appData.tourneyFile)) {
10123              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10124         }
10125         if(name[0] == NULLCHAR) {
10126             if(appData.participants[0])
10127                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10128             return 0;
10129         }
10130         f = fopen(name, "r");
10131         if(f) { // file exists
10132             ASSIGN(appData.tourneyFile, name);
10133             ParseArgsFromFile(f); // parse it
10134         } else {
10135             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10136             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10137                 DisplayError(_("Not enough participants"), 0);
10138                 return 0;
10139             }
10140             if(CheckPlayers(appData.participants)) return 0;
10141             ASSIGN(appData.tourneyFile, name);
10142             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10143             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10144         }
10145         fclose(f);
10146         appData.noChessProgram = FALSE;
10147         appData.clockMode = TRUE;
10148         SetGNUMode();
10149         return 1;
10150 }
10151
10152 int
10153 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10154 {
10155     char buf[MSG_SIZ], *p, *q;
10156     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10157     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10158     skip = !all && group[0]; // if group requested, we start in skip mode
10159     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10160         p = names; q = buf; header = 0;
10161         while(*p && *p != '\n') *q++ = *p++;
10162         *q = 0;
10163         if(*p == '\n') p++;
10164         if(buf[0] == '#') {
10165             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10166             depth++; // we must be entering a new group
10167             if(all) continue; // suppress printing group headers when complete list requested
10168             header = 1;
10169             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10170         }
10171         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10172         if(engineList[i]) free(engineList[i]);
10173         engineList[i] = strdup(buf);
10174         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10175         if(engineMnemonic[i]) free(engineMnemonic[i]);
10176         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10177             strcat(buf, " (");
10178             sscanf(q + 8, "%s", buf + strlen(buf));
10179             strcat(buf, ")");
10180         }
10181         engineMnemonic[i] = strdup(buf);
10182         i++;
10183     }
10184     engineList[i] = engineMnemonic[i] = NULL;
10185     return i;
10186 }
10187
10188 // following implemented as macro to avoid type limitations
10189 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10190
10191 void
10192 SwapEngines (int n)
10193 {   // swap settings for first engine and other engine (so far only some selected options)
10194     int h;
10195     char *p;
10196     if(n == 0) return;
10197     SWAP(directory, p)
10198     SWAP(chessProgram, p)
10199     SWAP(isUCI, h)
10200     SWAP(hasOwnBookUCI, h)
10201     SWAP(protocolVersion, h)
10202     SWAP(reuse, h)
10203     SWAP(scoreIsAbsolute, h)
10204     SWAP(timeOdds, h)
10205     SWAP(logo, p)
10206     SWAP(pgnName, p)
10207     SWAP(pvSAN, h)
10208     SWAP(engOptions, p)
10209     SWAP(engInitString, p)
10210     SWAP(computerString, p)
10211     SWAP(features, p)
10212     SWAP(fenOverride, p)
10213     SWAP(NPS, h)
10214     SWAP(accumulateTC, h)
10215     SWAP(host, p)
10216 }
10217
10218 int
10219 GetEngineLine (char *s, int n)
10220 {
10221     int i;
10222     char buf[MSG_SIZ];
10223     extern char *icsNames;
10224     if(!s || !*s) return 0;
10225     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10226     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10227     if(!mnemonic[i]) return 0;
10228     if(n == 11) return 1; // just testing if there was a match
10229     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10230     if(n == 1) SwapEngines(n);
10231     ParseArgsFromString(buf);
10232     if(n == 1) SwapEngines(n);
10233     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10234         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10235         ParseArgsFromString(buf);
10236     }
10237     return 1;
10238 }
10239
10240 int
10241 SetPlayer (int player, char *p)
10242 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10243     int i;
10244     char buf[MSG_SIZ], *engineName;
10245     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10246     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10247     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10248     if(mnemonic[i]) {
10249         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10250         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10251         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10252         ParseArgsFromString(buf);
10253     }
10254     free(engineName);
10255     return i;
10256 }
10257
10258 char *recentEngines;
10259
10260 void
10261 RecentEngineEvent (int nr)
10262 {
10263     int n;
10264 //    SwapEngines(1); // bump first to second
10265 //    ReplaceEngine(&second, 1); // and load it there
10266     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10267     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10268     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10269         ReplaceEngine(&first, 0);
10270         FloatToFront(&appData.recentEngineList, command[n]);
10271     }
10272 }
10273
10274 int
10275 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10276 {   // determine players from game number
10277     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10278
10279     if(appData.tourneyType == 0) {
10280         roundsPerCycle = (nPlayers - 1) | 1;
10281         pairingsPerRound = nPlayers / 2;
10282     } else if(appData.tourneyType > 0) {
10283         roundsPerCycle = nPlayers - appData.tourneyType;
10284         pairingsPerRound = appData.tourneyType;
10285     }
10286     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10287     gamesPerCycle = gamesPerRound * roundsPerCycle;
10288     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10289     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10290     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10291     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10292     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10293     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10294
10295     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10296     if(appData.roundSync) *syncInterval = gamesPerRound;
10297
10298     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10299
10300     if(appData.tourneyType == 0) {
10301         if(curPairing == (nPlayers-1)/2 ) {
10302             *whitePlayer = curRound;
10303             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10304         } else {
10305             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10306             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10307             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10308             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10309         }
10310     } else if(appData.tourneyType > 1) {
10311         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10312         *whitePlayer = curRound + appData.tourneyType;
10313     } else if(appData.tourneyType > 0) {
10314         *whitePlayer = curPairing;
10315         *blackPlayer = curRound + appData.tourneyType;
10316     }
10317
10318     // take care of white/black alternation per round. 
10319     // For cycles and games this is already taken care of by default, derived from matchGame!
10320     return curRound & 1;
10321 }
10322
10323 int
10324 NextTourneyGame (int nr, int *swapColors)
10325 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10326     char *p, *q;
10327     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10328     FILE *tf;
10329     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10330     tf = fopen(appData.tourneyFile, "r");
10331     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10332     ParseArgsFromFile(tf); fclose(tf);
10333     InitTimeControls(); // TC might be altered from tourney file
10334
10335     nPlayers = CountPlayers(appData.participants); // count participants
10336     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10337     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10338
10339     if(syncInterval) {
10340         p = q = appData.results;
10341         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10342         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10343             DisplayMessage(_("Waiting for other game(s)"),"");
10344             waitingForGame = TRUE;
10345             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10346             return 0;
10347         }
10348         waitingForGame = FALSE;
10349     }
10350
10351     if(appData.tourneyType < 0) {
10352         if(nr>=0 && !pairingReceived) {
10353             char buf[1<<16];
10354             if(pairing.pr == NoProc) {
10355                 if(!appData.pairingEngine[0]) {
10356                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10357                     return 0;
10358                 }
10359                 StartChessProgram(&pairing); // starts the pairing engine
10360             }
10361             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10362             SendToProgram(buf, &pairing);
10363             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10364             SendToProgram(buf, &pairing);
10365             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10366         }
10367         pairingReceived = 0;                              // ... so we continue here 
10368         *swapColors = 0;
10369         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10370         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10371         matchGame = 1; roundNr = nr / syncInterval + 1;
10372     }
10373
10374     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10375
10376     // redefine engines, engine dir, etc.
10377     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10378     if(first.pr == NoProc) {
10379       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10380       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10381     }
10382     if(second.pr == NoProc) {
10383       SwapEngines(1);
10384       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10385       SwapEngines(1);         // and make that valid for second engine by swapping
10386       InitEngine(&second, 1);
10387     }
10388     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10389     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10390     return 1;
10391 }
10392
10393 void
10394 NextMatchGame ()
10395 {   // performs game initialization that does not invoke engines, and then tries to start the game
10396     int res, firstWhite, swapColors = 0;
10397     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10398     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
10399         char buf[MSG_SIZ];
10400         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10401         if(strcmp(buf, currentDebugFile)) { // name has changed
10402             FILE *f = fopen(buf, "w");
10403             if(f) { // if opening the new file failed, just keep using the old one
10404                 ASSIGN(currentDebugFile, buf);
10405                 fclose(debugFP);
10406                 debugFP = f;
10407             }
10408             if(appData.serverFileName) {
10409                 if(serverFP) fclose(serverFP);
10410                 serverFP = fopen(appData.serverFileName, "w");
10411                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10412                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10413             }
10414         }
10415     }
10416     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10417     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10418     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10419     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10420     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10421     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10422     Reset(FALSE, first.pr != NoProc);
10423     res = LoadGameOrPosition(matchGame); // setup game
10424     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10425     if(!res) return; // abort when bad game/pos file
10426     TwoMachinesEvent();
10427 }
10428
10429 void
10430 UserAdjudicationEvent (int result)
10431 {
10432     ChessMove gameResult = GameIsDrawn;
10433
10434     if( result > 0 ) {
10435         gameResult = WhiteWins;
10436     }
10437     else if( result < 0 ) {
10438         gameResult = BlackWins;
10439     }
10440
10441     if( gameMode == TwoMachinesPlay ) {
10442         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10443     }
10444 }
10445
10446
10447 // [HGM] save: calculate checksum of game to make games easily identifiable
10448 int
10449 StringCheckSum (char *s)
10450 {
10451         int i = 0;
10452         if(s==NULL) return 0;
10453         while(*s) i = i*259 + *s++;
10454         return i;
10455 }
10456
10457 int
10458 GameCheckSum ()
10459 {
10460         int i, sum=0;
10461         for(i=backwardMostMove; i<forwardMostMove; i++) {
10462                 sum += pvInfoList[i].depth;
10463                 sum += StringCheckSum(parseList[i]);
10464                 sum += StringCheckSum(commentList[i]);
10465                 sum *= 261;
10466         }
10467         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10468         return sum + StringCheckSum(commentList[i]);
10469 } // end of save patch
10470
10471 void
10472 GameEnds (ChessMove result, char *resultDetails, int whosays)
10473 {
10474     GameMode nextGameMode;
10475     int isIcsGame;
10476     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10477
10478     if(endingGame) return; /* [HGM] crash: forbid recursion */
10479     endingGame = 1;
10480     if(twoBoards) { // [HGM] dual: switch back to one board
10481         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10482         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10483     }
10484     if (appData.debugMode) {
10485       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10486               result, resultDetails ? resultDetails : "(null)", whosays);
10487     }
10488
10489     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10490
10491     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10492         /* If we are playing on ICS, the server decides when the
10493            game is over, but the engine can offer to draw, claim
10494            a draw, or resign.
10495          */
10496 #if ZIPPY
10497         if (appData.zippyPlay && first.initDone) {
10498             if (result == GameIsDrawn) {
10499                 /* In case draw still needs to be claimed */
10500                 SendToICS(ics_prefix);
10501                 SendToICS("draw\n");
10502             } else if (StrCaseStr(resultDetails, "resign")) {
10503                 SendToICS(ics_prefix);
10504                 SendToICS("resign\n");
10505             }
10506         }
10507 #endif
10508         endingGame = 0; /* [HGM] crash */
10509         return;
10510     }
10511
10512     /* If we're loading the game from a file, stop */
10513     if (whosays == GE_FILE) {
10514       (void) StopLoadGameTimer();
10515       gameFileFP = NULL;
10516     }
10517
10518     /* Cancel draw offers */
10519     first.offeredDraw = second.offeredDraw = 0;
10520
10521     /* If this is an ICS game, only ICS can really say it's done;
10522        if not, anyone can. */
10523     isIcsGame = (gameMode == IcsPlayingWhite ||
10524                  gameMode == IcsPlayingBlack ||
10525                  gameMode == IcsObserving    ||
10526                  gameMode == IcsExamining);
10527
10528     if (!isIcsGame || whosays == GE_ICS) {
10529         /* OK -- not an ICS game, or ICS said it was done */
10530         StopClocks();
10531         if (!isIcsGame && !appData.noChessProgram)
10532           SetUserThinkingEnables();
10533
10534         /* [HGM] if a machine claims the game end we verify this claim */
10535         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10536             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10537                 char claimer;
10538                 ChessMove trueResult = (ChessMove) -1;
10539
10540                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10541                                             first.twoMachinesColor[0] :
10542                                             second.twoMachinesColor[0] ;
10543
10544                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10545                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10546                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10547                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10548                 } else
10549                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10550                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10551                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10552                 } else
10553                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10554                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10555                 }
10556
10557                 // now verify win claims, but not in drop games, as we don't understand those yet
10558                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10559                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10560                     (result == WhiteWins && claimer == 'w' ||
10561                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10562                       if (appData.debugMode) {
10563                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10564                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10565                       }
10566                       if(result != trueResult) {
10567                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10568                               result = claimer == 'w' ? BlackWins : WhiteWins;
10569                               resultDetails = buf;
10570                       }
10571                 } else
10572                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10573                     && (forwardMostMove <= backwardMostMove ||
10574                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10575                         (claimer=='b')==(forwardMostMove&1))
10576                                                                                   ) {
10577                       /* [HGM] verify: draws that were not flagged are false claims */
10578                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10579                       result = claimer == 'w' ? BlackWins : WhiteWins;
10580                       resultDetails = buf;
10581                 }
10582                 /* (Claiming a loss is accepted no questions asked!) */
10583             }
10584             /* [HGM] bare: don't allow bare King to win */
10585             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10586                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10587                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10588                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10589                && result != GameIsDrawn)
10590             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10591                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10592                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10593                         if(p >= 0 && p <= (int)WhiteKing) k++;
10594                 }
10595                 if (appData.debugMode) {
10596                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10597                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10598                 }
10599                 if(k <= 1) {
10600                         result = GameIsDrawn;
10601                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10602                         resultDetails = buf;
10603                 }
10604             }
10605         }
10606
10607
10608         if(serverMoves != NULL && !loadFlag) { char c = '=';
10609             if(result==WhiteWins) c = '+';
10610             if(result==BlackWins) c = '-';
10611             if(resultDetails != NULL)
10612                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10613         }
10614         if (resultDetails != NULL) {
10615             gameInfo.result = result;
10616             gameInfo.resultDetails = StrSave(resultDetails);
10617
10618             /* display last move only if game was not loaded from file */
10619             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10620                 DisplayMove(currentMove - 1);
10621
10622             if (forwardMostMove != 0) {
10623                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10624                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10625                                                                 ) {
10626                     if (*appData.saveGameFile != NULLCHAR) {
10627                         SaveGameToFile(appData.saveGameFile, TRUE);
10628                     } else if (appData.autoSaveGames) {
10629                         AutoSaveGame();
10630                     }
10631                     if (*appData.savePositionFile != NULLCHAR) {
10632                         SavePositionToFile(appData.savePositionFile);
10633                     }
10634                 }
10635             }
10636
10637             /* Tell program how game ended in case it is learning */
10638             /* [HGM] Moved this to after saving the PGN, just in case */
10639             /* engine died and we got here through time loss. In that */
10640             /* case we will get a fatal error writing the pipe, which */
10641             /* would otherwise lose us the PGN.                       */
10642             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10643             /* output during GameEnds should never be fatal anymore   */
10644             if (gameMode == MachinePlaysWhite ||
10645                 gameMode == MachinePlaysBlack ||
10646                 gameMode == TwoMachinesPlay ||
10647                 gameMode == IcsPlayingWhite ||
10648                 gameMode == IcsPlayingBlack ||
10649                 gameMode == BeginningOfGame) {
10650                 char buf[MSG_SIZ];
10651                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10652                         resultDetails);
10653                 if (first.pr != NoProc) {
10654                     SendToProgram(buf, &first);
10655                 }
10656                 if (second.pr != NoProc &&
10657                     gameMode == TwoMachinesPlay) {
10658                     SendToProgram(buf, &second);
10659                 }
10660             }
10661         }
10662
10663         if (appData.icsActive) {
10664             if (appData.quietPlay &&
10665                 (gameMode == IcsPlayingWhite ||
10666                  gameMode == IcsPlayingBlack)) {
10667                 SendToICS(ics_prefix);
10668                 SendToICS("set shout 1\n");
10669             }
10670             nextGameMode = IcsIdle;
10671             ics_user_moved = FALSE;
10672             /* clean up premove.  It's ugly when the game has ended and the
10673              * premove highlights are still on the board.
10674              */
10675             if (gotPremove) {
10676               gotPremove = FALSE;
10677               ClearPremoveHighlights();
10678               DrawPosition(FALSE, boards[currentMove]);
10679             }
10680             if (whosays == GE_ICS) {
10681                 switch (result) {
10682                 case WhiteWins:
10683                     if (gameMode == IcsPlayingWhite)
10684                         PlayIcsWinSound();
10685                     else if(gameMode == IcsPlayingBlack)
10686                         PlayIcsLossSound();
10687                     break;
10688                 case BlackWins:
10689                     if (gameMode == IcsPlayingBlack)
10690                         PlayIcsWinSound();
10691                     else if(gameMode == IcsPlayingWhite)
10692                         PlayIcsLossSound();
10693                     break;
10694                 case GameIsDrawn:
10695                     PlayIcsDrawSound();
10696                     break;
10697                 default:
10698                     PlayIcsUnfinishedSound();
10699                 }
10700             }
10701         } else if (gameMode == EditGame ||
10702                    gameMode == PlayFromGameFile ||
10703                    gameMode == AnalyzeMode ||
10704                    gameMode == AnalyzeFile) {
10705             nextGameMode = gameMode;
10706         } else {
10707             nextGameMode = EndOfGame;
10708         }
10709         pausing = FALSE;
10710         ModeHighlight();
10711     } else {
10712         nextGameMode = gameMode;
10713     }
10714
10715     if (appData.noChessProgram) {
10716         gameMode = nextGameMode;
10717         ModeHighlight();
10718         endingGame = 0; /* [HGM] crash */
10719         return;
10720     }
10721
10722     if (first.reuse) {
10723         /* Put first chess program into idle state */
10724         if (first.pr != NoProc &&
10725             (gameMode == MachinePlaysWhite ||
10726              gameMode == MachinePlaysBlack ||
10727              gameMode == TwoMachinesPlay ||
10728              gameMode == IcsPlayingWhite ||
10729              gameMode == IcsPlayingBlack ||
10730              gameMode == BeginningOfGame)) {
10731             SendToProgram("force\n", &first);
10732             if (first.usePing) {
10733               char buf[MSG_SIZ];
10734               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10735               SendToProgram(buf, &first);
10736             }
10737         }
10738     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10739         /* Kill off first chess program */
10740         if (first.isr != NULL)
10741           RemoveInputSource(first.isr);
10742         first.isr = NULL;
10743
10744         if (first.pr != NoProc) {
10745             ExitAnalyzeMode();
10746             DoSleep( appData.delayBeforeQuit );
10747             SendToProgram("quit\n", &first);
10748             DoSleep( appData.delayAfterQuit );
10749             DestroyChildProcess(first.pr, first.useSigterm);
10750         }
10751         first.pr = NoProc;
10752     }
10753     if (second.reuse) {
10754         /* Put second chess program into idle state */
10755         if (second.pr != NoProc &&
10756             gameMode == TwoMachinesPlay) {
10757             SendToProgram("force\n", &second);
10758             if (second.usePing) {
10759               char buf[MSG_SIZ];
10760               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10761               SendToProgram(buf, &second);
10762             }
10763         }
10764     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10765         /* Kill off second chess program */
10766         if (second.isr != NULL)
10767           RemoveInputSource(second.isr);
10768         second.isr = NULL;
10769
10770         if (second.pr != NoProc) {
10771             DoSleep( appData.delayBeforeQuit );
10772             SendToProgram("quit\n", &second);
10773             DoSleep( appData.delayAfterQuit );
10774             DestroyChildProcess(second.pr, second.useSigterm);
10775         }
10776         second.pr = NoProc;
10777     }
10778
10779     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10780         char resChar = '=';
10781         switch (result) {
10782         case WhiteWins:
10783           resChar = '+';
10784           if (first.twoMachinesColor[0] == 'w') {
10785             first.matchWins++;
10786           } else {
10787             second.matchWins++;
10788           }
10789           break;
10790         case BlackWins:
10791           resChar = '-';
10792           if (first.twoMachinesColor[0] == 'b') {
10793             first.matchWins++;
10794           } else {
10795             second.matchWins++;
10796           }
10797           break;
10798         case GameUnfinished:
10799           resChar = ' ';
10800         default:
10801           break;
10802         }
10803
10804         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10805         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10806             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10807             ReserveGame(nextGame, resChar); // sets nextGame
10808             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10809             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10810         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10811
10812         if (nextGame <= appData.matchGames && !abortMatch) {
10813             gameMode = nextGameMode;
10814             matchGame = nextGame; // this will be overruled in tourney mode!
10815             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10816             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10817             endingGame = 0; /* [HGM] crash */
10818             return;
10819         } else {
10820             gameMode = nextGameMode;
10821             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10822                      first.tidy, second.tidy,
10823                      first.matchWins, second.matchWins,
10824                      appData.matchGames - (first.matchWins + second.matchWins));
10825             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10826             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10827             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10828             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10829                 first.twoMachinesColor = "black\n";
10830                 second.twoMachinesColor = "white\n";
10831             } else {
10832                 first.twoMachinesColor = "white\n";
10833                 second.twoMachinesColor = "black\n";
10834             }
10835         }
10836     }
10837     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10838         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10839       ExitAnalyzeMode();
10840     gameMode = nextGameMode;
10841     ModeHighlight();
10842     endingGame = 0;  /* [HGM] crash */
10843     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10844         if(matchMode == TRUE) { // match through command line: exit with or without popup
10845             if(ranking) {
10846                 ToNrEvent(forwardMostMove);
10847                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10848                 else ExitEvent(0);
10849             } else DisplayFatalError(buf, 0, 0);
10850         } else { // match through menu; just stop, with or without popup
10851             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10852             ModeHighlight();
10853             if(ranking){
10854                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10855             } else DisplayNote(buf);
10856       }
10857       if(ranking) free(ranking);
10858     }
10859 }
10860
10861 /* Assumes program was just initialized (initString sent).
10862    Leaves program in force mode. */
10863 void
10864 FeedMovesToProgram (ChessProgramState *cps, int upto)
10865 {
10866     int i;
10867
10868     if (appData.debugMode)
10869       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10870               startedFromSetupPosition ? "position and " : "",
10871               backwardMostMove, upto, cps->which);
10872     if(currentlyInitializedVariant != gameInfo.variant) {
10873       char buf[MSG_SIZ];
10874         // [HGM] variantswitch: make engine aware of new variant
10875         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10876                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10877         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10878         SendToProgram(buf, cps);
10879         currentlyInitializedVariant = gameInfo.variant;
10880     }
10881     SendToProgram("force\n", cps);
10882     if (startedFromSetupPosition) {
10883         SendBoard(cps, backwardMostMove);
10884     if (appData.debugMode) {
10885         fprintf(debugFP, "feedMoves\n");
10886     }
10887     }
10888     for (i = backwardMostMove; i < upto; i++) {
10889         SendMoveToProgram(i, cps);
10890     }
10891 }
10892
10893
10894 int
10895 ResurrectChessProgram ()
10896 {
10897      /* The chess program may have exited.
10898         If so, restart it and feed it all the moves made so far. */
10899     static int doInit = 0;
10900
10901     if (appData.noChessProgram) return 1;
10902
10903     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10904         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10905         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10906         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10907     } else {
10908         if (first.pr != NoProc) return 1;
10909         StartChessProgram(&first);
10910     }
10911     InitChessProgram(&first, FALSE);
10912     FeedMovesToProgram(&first, currentMove);
10913
10914     if (!first.sendTime) {
10915         /* can't tell gnuchess what its clock should read,
10916            so we bow to its notion. */
10917         ResetClocks();
10918         timeRemaining[0][currentMove] = whiteTimeRemaining;
10919         timeRemaining[1][currentMove] = blackTimeRemaining;
10920     }
10921
10922     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10923                 appData.icsEngineAnalyze) && first.analysisSupport) {
10924       SendToProgram("analyze\n", &first);
10925       first.analyzing = TRUE;
10926     }
10927     return 1;
10928 }
10929
10930 /*
10931  * Button procedures
10932  */
10933 void
10934 Reset (int redraw, int init)
10935 {
10936     int i;
10937
10938     if (appData.debugMode) {
10939         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10940                 redraw, init, gameMode);
10941     }
10942     CleanupTail(); // [HGM] vari: delete any stored variations
10943     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10944     pausing = pauseExamInvalid = FALSE;
10945     startedFromSetupPosition = blackPlaysFirst = FALSE;
10946     firstMove = TRUE;
10947     whiteFlag = blackFlag = FALSE;
10948     userOfferedDraw = FALSE;
10949     hintRequested = bookRequested = FALSE;
10950     first.maybeThinking = FALSE;
10951     second.maybeThinking = FALSE;
10952     first.bookSuspend = FALSE; // [HGM] book
10953     second.bookSuspend = FALSE;
10954     thinkOutput[0] = NULLCHAR;
10955     lastHint[0] = NULLCHAR;
10956     ClearGameInfo(&gameInfo);
10957     gameInfo.variant = StringToVariant(appData.variant);
10958     ics_user_moved = ics_clock_paused = FALSE;
10959     ics_getting_history = H_FALSE;
10960     ics_gamenum = -1;
10961     white_holding[0] = black_holding[0] = NULLCHAR;
10962     ClearProgramStats();
10963     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10964
10965     ResetFrontEnd();
10966     ClearHighlights();
10967     flipView = appData.flipView;
10968     ClearPremoveHighlights();
10969     gotPremove = FALSE;
10970     alarmSounded = FALSE;
10971
10972     GameEnds(EndOfFile, NULL, GE_PLAYER);
10973     if(appData.serverMovesName != NULL) {
10974         /* [HGM] prepare to make moves file for broadcasting */
10975         clock_t t = clock();
10976         if(serverMoves != NULL) fclose(serverMoves);
10977         serverMoves = fopen(appData.serverMovesName, "r");
10978         if(serverMoves != NULL) {
10979             fclose(serverMoves);
10980             /* delay 15 sec before overwriting, so all clients can see end */
10981             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10982         }
10983         serverMoves = fopen(appData.serverMovesName, "w");
10984     }
10985
10986     ExitAnalyzeMode();
10987     gameMode = BeginningOfGame;
10988     ModeHighlight();
10989     if(appData.icsActive) gameInfo.variant = VariantNormal;
10990     currentMove = forwardMostMove = backwardMostMove = 0;
10991     MarkTargetSquares(1);
10992     InitPosition(redraw);
10993     for (i = 0; i < MAX_MOVES; i++) {
10994         if (commentList[i] != NULL) {
10995             free(commentList[i]);
10996             commentList[i] = NULL;
10997         }
10998     }
10999     ResetClocks();
11000     timeRemaining[0][0] = whiteTimeRemaining;
11001     timeRemaining[1][0] = blackTimeRemaining;
11002
11003     if (first.pr == NoProc) {
11004         StartChessProgram(&first);
11005     }
11006     if (init) {
11007             InitChessProgram(&first, startedFromSetupPosition);
11008     }
11009     DisplayTitle("");
11010     DisplayMessage("", "");
11011     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11012     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11013     ClearMap();        // [HGM] exclude: invalidate map
11014 }
11015
11016 void
11017 AutoPlayGameLoop ()
11018 {
11019     for (;;) {
11020         if (!AutoPlayOneMove())
11021           return;
11022         if (matchMode || appData.timeDelay == 0)
11023           continue;
11024         if (appData.timeDelay < 0)
11025           return;
11026         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11027         break;
11028     }
11029 }
11030
11031 void
11032 AnalyzeNextGame()
11033 {
11034     ReloadGame(1); // next game
11035 }
11036
11037 int
11038 AutoPlayOneMove ()
11039 {
11040     int fromX, fromY, toX, toY;
11041
11042     if (appData.debugMode) {
11043       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11044     }
11045
11046     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11047       return FALSE;
11048
11049     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11050       pvInfoList[currentMove].depth = programStats.depth;
11051       pvInfoList[currentMove].score = programStats.score;
11052       pvInfoList[currentMove].time  = 0;
11053       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11054     }
11055
11056     if (currentMove >= forwardMostMove) {
11057       if(gameMode == AnalyzeFile) {
11058           if(appData.loadGameIndex == -1) {
11059             GameEnds(EndOfFile, NULL, GE_FILE);
11060           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11061           } else {
11062           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11063         }
11064       }
11065 //      gameMode = EndOfGame;
11066 //      ModeHighlight();
11067
11068       /* [AS] Clear current move marker at the end of a game */
11069       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11070
11071       return FALSE;
11072     }
11073
11074     toX = moveList[currentMove][2] - AAA;
11075     toY = moveList[currentMove][3] - ONE;
11076
11077     if (moveList[currentMove][1] == '@') {
11078         if (appData.highlightLastMove) {
11079             SetHighlights(-1, -1, toX, toY);
11080         }
11081     } else {
11082         fromX = moveList[currentMove][0] - AAA;
11083         fromY = moveList[currentMove][1] - ONE;
11084
11085         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11086
11087         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11088
11089         if (appData.highlightLastMove) {
11090             SetHighlights(fromX, fromY, toX, toY);
11091         }
11092     }
11093     DisplayMove(currentMove);
11094     SendMoveToProgram(currentMove++, &first);
11095     DisplayBothClocks();
11096     DrawPosition(FALSE, boards[currentMove]);
11097     // [HGM] PV info: always display, routine tests if empty
11098     DisplayComment(currentMove - 1, commentList[currentMove]);
11099     return TRUE;
11100 }
11101
11102
11103 int
11104 LoadGameOneMove (ChessMove readAhead)
11105 {
11106     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11107     char promoChar = NULLCHAR;
11108     ChessMove moveType;
11109     char move[MSG_SIZ];
11110     char *p, *q;
11111
11112     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11113         gameMode != AnalyzeMode && gameMode != Training) {
11114         gameFileFP = NULL;
11115         return FALSE;
11116     }
11117
11118     yyboardindex = forwardMostMove;
11119     if (readAhead != EndOfFile) {
11120       moveType = readAhead;
11121     } else {
11122       if (gameFileFP == NULL)
11123           return FALSE;
11124       moveType = (ChessMove) Myylex();
11125     }
11126
11127     done = FALSE;
11128     switch (moveType) {
11129       case Comment:
11130         if (appData.debugMode)
11131           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11132         p = yy_text;
11133
11134         /* append the comment but don't display it */
11135         AppendComment(currentMove, p, FALSE);
11136         return TRUE;
11137
11138       case WhiteCapturesEnPassant:
11139       case BlackCapturesEnPassant:
11140       case WhitePromotion:
11141       case BlackPromotion:
11142       case WhiteNonPromotion:
11143       case BlackNonPromotion:
11144       case NormalMove:
11145       case WhiteKingSideCastle:
11146       case WhiteQueenSideCastle:
11147       case BlackKingSideCastle:
11148       case BlackQueenSideCastle:
11149       case WhiteKingSideCastleWild:
11150       case WhiteQueenSideCastleWild:
11151       case BlackKingSideCastleWild:
11152       case BlackQueenSideCastleWild:
11153       /* PUSH Fabien */
11154       case WhiteHSideCastleFR:
11155       case WhiteASideCastleFR:
11156       case BlackHSideCastleFR:
11157       case BlackASideCastleFR:
11158       /* POP Fabien */
11159         if (appData.debugMode)
11160           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11161         fromX = currentMoveString[0] - AAA;
11162         fromY = currentMoveString[1] - ONE;
11163         toX = currentMoveString[2] - AAA;
11164         toY = currentMoveString[3] - ONE;
11165         promoChar = currentMoveString[4];
11166         break;
11167
11168       case WhiteDrop:
11169       case BlackDrop:
11170         if (appData.debugMode)
11171           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11172         fromX = moveType == WhiteDrop ?
11173           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11174         (int) CharToPiece(ToLower(currentMoveString[0]));
11175         fromY = DROP_RANK;
11176         toX = currentMoveString[2] - AAA;
11177         toY = currentMoveString[3] - ONE;
11178         break;
11179
11180       case WhiteWins:
11181       case BlackWins:
11182       case GameIsDrawn:
11183       case GameUnfinished:
11184         if (appData.debugMode)
11185           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11186         p = strchr(yy_text, '{');
11187         if (p == NULL) p = strchr(yy_text, '(');
11188         if (p == NULL) {
11189             p = yy_text;
11190             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11191         } else {
11192             q = strchr(p, *p == '{' ? '}' : ')');
11193             if (q != NULL) *q = NULLCHAR;
11194             p++;
11195         }
11196         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11197         GameEnds(moveType, p, GE_FILE);
11198         done = TRUE;
11199         if (cmailMsgLoaded) {
11200             ClearHighlights();
11201             flipView = WhiteOnMove(currentMove);
11202             if (moveType == GameUnfinished) flipView = !flipView;
11203             if (appData.debugMode)
11204               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11205         }
11206         break;
11207
11208       case EndOfFile:
11209         if (appData.debugMode)
11210           fprintf(debugFP, "Parser hit end of file\n");
11211         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11212           case MT_NONE:
11213           case MT_CHECK:
11214             break;
11215           case MT_CHECKMATE:
11216           case MT_STAINMATE:
11217             if (WhiteOnMove(currentMove)) {
11218                 GameEnds(BlackWins, "Black mates", GE_FILE);
11219             } else {
11220                 GameEnds(WhiteWins, "White mates", GE_FILE);
11221             }
11222             break;
11223           case MT_STALEMATE:
11224             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11225             break;
11226         }
11227         done = TRUE;
11228         break;
11229
11230       case MoveNumberOne:
11231         if (lastLoadGameStart == GNUChessGame) {
11232             /* GNUChessGames have numbers, but they aren't move numbers */
11233             if (appData.debugMode)
11234               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11235                       yy_text, (int) moveType);
11236             return LoadGameOneMove(EndOfFile); /* tail recursion */
11237         }
11238         /* else fall thru */
11239
11240       case XBoardGame:
11241       case GNUChessGame:
11242       case PGNTag:
11243         /* Reached start of next game in file */
11244         if (appData.debugMode)
11245           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11246         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11247           case MT_NONE:
11248           case MT_CHECK:
11249             break;
11250           case MT_CHECKMATE:
11251           case MT_STAINMATE:
11252             if (WhiteOnMove(currentMove)) {
11253                 GameEnds(BlackWins, "Black mates", GE_FILE);
11254             } else {
11255                 GameEnds(WhiteWins, "White mates", GE_FILE);
11256             }
11257             break;
11258           case MT_STALEMATE:
11259             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11260             break;
11261         }
11262         done = TRUE;
11263         break;
11264
11265       case PositionDiagram:     /* should not happen; ignore */
11266       case ElapsedTime:         /* ignore */
11267       case NAG:                 /* ignore */
11268         if (appData.debugMode)
11269           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11270                   yy_text, (int) moveType);
11271         return LoadGameOneMove(EndOfFile); /* tail recursion */
11272
11273       case IllegalMove:
11274         if (appData.testLegality) {
11275             if (appData.debugMode)
11276               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11277             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11278                     (forwardMostMove / 2) + 1,
11279                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11280             DisplayError(move, 0);
11281             done = TRUE;
11282         } else {
11283             if (appData.debugMode)
11284               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11285                       yy_text, currentMoveString);
11286             fromX = currentMoveString[0] - AAA;
11287             fromY = currentMoveString[1] - ONE;
11288             toX = currentMoveString[2] - AAA;
11289             toY = currentMoveString[3] - ONE;
11290             promoChar = currentMoveString[4];
11291         }
11292         break;
11293
11294       case AmbiguousMove:
11295         if (appData.debugMode)
11296           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11297         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11298                 (forwardMostMove / 2) + 1,
11299                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11300         DisplayError(move, 0);
11301         done = TRUE;
11302         break;
11303
11304       default:
11305       case ImpossibleMove:
11306         if (appData.debugMode)
11307           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11308         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11309                 (forwardMostMove / 2) + 1,
11310                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11311         DisplayError(move, 0);
11312         done = TRUE;
11313         break;
11314     }
11315
11316     if (done) {
11317         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11318             DrawPosition(FALSE, boards[currentMove]);
11319             DisplayBothClocks();
11320             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11321               DisplayComment(currentMove - 1, commentList[currentMove]);
11322         }
11323         (void) StopLoadGameTimer();
11324         gameFileFP = NULL;
11325         cmailOldMove = forwardMostMove;
11326         return FALSE;
11327     } else {
11328         /* currentMoveString is set as a side-effect of yylex */
11329
11330         thinkOutput[0] = NULLCHAR;
11331         MakeMove(fromX, fromY, toX, toY, promoChar);
11332         currentMove = forwardMostMove;
11333         return TRUE;
11334     }
11335 }
11336
11337 /* Load the nth game from the given file */
11338 int
11339 LoadGameFromFile (char *filename, int n, char *title, int useList)
11340 {
11341     FILE *f;
11342     char buf[MSG_SIZ];
11343
11344     if (strcmp(filename, "-") == 0) {
11345         f = stdin;
11346         title = "stdin";
11347     } else {
11348         f = fopen(filename, "rb");
11349         if (f == NULL) {
11350           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11351             DisplayError(buf, errno);
11352             return FALSE;
11353         }
11354     }
11355     if (fseek(f, 0, 0) == -1) {
11356         /* f is not seekable; probably a pipe */
11357         useList = FALSE;
11358     }
11359     if (useList && n == 0) {
11360         int error = GameListBuild(f);
11361         if (error) {
11362             DisplayError(_("Cannot build game list"), error);
11363         } else if (!ListEmpty(&gameList) &&
11364                    ((ListGame *) gameList.tailPred)->number > 1) {
11365             GameListPopUp(f, title);
11366             return TRUE;
11367         }
11368         GameListDestroy();
11369         n = 1;
11370     }
11371     if (n == 0) n = 1;
11372     return LoadGame(f, n, title, FALSE);
11373 }
11374
11375
11376 void
11377 MakeRegisteredMove ()
11378 {
11379     int fromX, fromY, toX, toY;
11380     char promoChar;
11381     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11382         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11383           case CMAIL_MOVE:
11384           case CMAIL_DRAW:
11385             if (appData.debugMode)
11386               fprintf(debugFP, "Restoring %s for game %d\n",
11387                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11388
11389             thinkOutput[0] = NULLCHAR;
11390             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11391             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11392             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11393             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11394             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11395             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11396             MakeMove(fromX, fromY, toX, toY, promoChar);
11397             ShowMove(fromX, fromY, toX, toY);
11398
11399             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11400               case MT_NONE:
11401               case MT_CHECK:
11402                 break;
11403
11404               case MT_CHECKMATE:
11405               case MT_STAINMATE:
11406                 if (WhiteOnMove(currentMove)) {
11407                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11408                 } else {
11409                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11410                 }
11411                 break;
11412
11413               case MT_STALEMATE:
11414                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11415                 break;
11416             }
11417
11418             break;
11419
11420           case CMAIL_RESIGN:
11421             if (WhiteOnMove(currentMove)) {
11422                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11423             } else {
11424                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11425             }
11426             break;
11427
11428           case CMAIL_ACCEPT:
11429             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11430             break;
11431
11432           default:
11433             break;
11434         }
11435     }
11436
11437     return;
11438 }
11439
11440 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11441 int
11442 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11443 {
11444     int retVal;
11445
11446     if (gameNumber > nCmailGames) {
11447         DisplayError(_("No more games in this message"), 0);
11448         return FALSE;
11449     }
11450     if (f == lastLoadGameFP) {
11451         int offset = gameNumber - lastLoadGameNumber;
11452         if (offset == 0) {
11453             cmailMsg[0] = NULLCHAR;
11454             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11455                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11456                 nCmailMovesRegistered--;
11457             }
11458             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11459             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11460                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11461             }
11462         } else {
11463             if (! RegisterMove()) return FALSE;
11464         }
11465     }
11466
11467     retVal = LoadGame(f, gameNumber, title, useList);
11468
11469     /* Make move registered during previous look at this game, if any */
11470     MakeRegisteredMove();
11471
11472     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11473         commentList[currentMove]
11474           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11475         DisplayComment(currentMove - 1, commentList[currentMove]);
11476     }
11477
11478     return retVal;
11479 }
11480
11481 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11482 int
11483 ReloadGame (int offset)
11484 {
11485     int gameNumber = lastLoadGameNumber + offset;
11486     if (lastLoadGameFP == NULL) {
11487         DisplayError(_("No game has been loaded yet"), 0);
11488         return FALSE;
11489     }
11490     if (gameNumber <= 0) {
11491         DisplayError(_("Can't back up any further"), 0);
11492         return FALSE;
11493     }
11494     if (cmailMsgLoaded) {
11495         return CmailLoadGame(lastLoadGameFP, gameNumber,
11496                              lastLoadGameTitle, lastLoadGameUseList);
11497     } else {
11498         return LoadGame(lastLoadGameFP, gameNumber,
11499                         lastLoadGameTitle, lastLoadGameUseList);
11500     }
11501 }
11502
11503 int keys[EmptySquare+1];
11504
11505 int
11506 PositionMatches (Board b1, Board b2)
11507 {
11508     int r, f, sum=0;
11509     switch(appData.searchMode) {
11510         case 1: return CompareWithRights(b1, b2);
11511         case 2:
11512             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11513                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11514             }
11515             return TRUE;
11516         case 3:
11517             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11518               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11519                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11520             }
11521             return sum==0;
11522         case 4:
11523             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11524                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11525             }
11526             return sum==0;
11527     }
11528     return TRUE;
11529 }
11530
11531 #define Q_PROMO  4
11532 #define Q_EP     3
11533 #define Q_BCASTL 2
11534 #define Q_WCASTL 1
11535
11536 int pieceList[256], quickBoard[256];
11537 ChessSquare pieceType[256] = { EmptySquare };
11538 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11539 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11540 int soughtTotal, turn;
11541 Boolean epOK, flipSearch;
11542
11543 typedef struct {
11544     unsigned char piece, to;
11545 } Move;
11546
11547 #define DSIZE (250000)
11548
11549 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11550 Move *moveDatabase = initialSpace;
11551 unsigned int movePtr, dataSize = DSIZE;
11552
11553 int
11554 MakePieceList (Board board, int *counts)
11555 {
11556     int r, f, n=Q_PROMO, total=0;
11557     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11558     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11559         int sq = f + (r<<4);
11560         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11561             quickBoard[sq] = ++n;
11562             pieceList[n] = sq;
11563             pieceType[n] = board[r][f];
11564             counts[board[r][f]]++;
11565             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11566             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11567             total++;
11568         }
11569     }
11570     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11571     return total;
11572 }
11573
11574 void
11575 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11576 {
11577     int sq = fromX + (fromY<<4);
11578     int piece = quickBoard[sq];
11579     quickBoard[sq] = 0;
11580     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11581     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11582         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11583         moveDatabase[movePtr++].piece = Q_WCASTL;
11584         quickBoard[sq] = piece;
11585         piece = quickBoard[from]; quickBoard[from] = 0;
11586         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11587     } else
11588     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11589         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11590         moveDatabase[movePtr++].piece = Q_BCASTL;
11591         quickBoard[sq] = piece;
11592         piece = quickBoard[from]; quickBoard[from] = 0;
11593         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11594     } else
11595     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11596         quickBoard[(fromY<<4)+toX] = 0;
11597         moveDatabase[movePtr].piece = Q_EP;
11598         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11599         moveDatabase[movePtr].to = sq;
11600     } else
11601     if(promoPiece != pieceType[piece]) {
11602         moveDatabase[movePtr++].piece = Q_PROMO;
11603         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11604     }
11605     moveDatabase[movePtr].piece = piece;
11606     quickBoard[sq] = piece;
11607     movePtr++;
11608 }
11609
11610 int
11611 PackGame (Board board)
11612 {
11613     Move *newSpace = NULL;
11614     moveDatabase[movePtr].piece = 0; // terminate previous game
11615     if(movePtr > dataSize) {
11616         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11617         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11618         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11619         if(newSpace) {
11620             int i;
11621             Move *p = moveDatabase, *q = newSpace;
11622             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11623             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11624             moveDatabase = newSpace;
11625         } else { // calloc failed, we must be out of memory. Too bad...
11626             dataSize = 0; // prevent calloc events for all subsequent games
11627             return 0;     // and signal this one isn't cached
11628         }
11629     }
11630     movePtr++;
11631     MakePieceList(board, counts);
11632     return movePtr;
11633 }
11634
11635 int
11636 QuickCompare (Board board, int *minCounts, int *maxCounts)
11637 {   // compare according to search mode
11638     int r, f;
11639     switch(appData.searchMode)
11640     {
11641       case 1: // exact position match
11642         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11643         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11644             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11645         }
11646         break;
11647       case 2: // can have extra material on empty squares
11648         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11649             if(board[r][f] == EmptySquare) continue;
11650             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11651         }
11652         break;
11653       case 3: // material with exact Pawn structure
11654         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11655             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11656             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11657         } // fall through to material comparison
11658       case 4: // exact material
11659         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11660         break;
11661       case 6: // material range with given imbalance
11662         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11663         // fall through to range comparison
11664       case 5: // material range
11665         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11666     }
11667     return TRUE;
11668 }
11669
11670 int
11671 QuickScan (Board board, Move *move)
11672 {   // reconstruct game,and compare all positions in it
11673     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11674     do {
11675         int piece = move->piece;
11676         int to = move->to, from = pieceList[piece];
11677         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11678           if(!piece) return -1;
11679           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11680             piece = (++move)->piece;
11681             from = pieceList[piece];
11682             counts[pieceType[piece]]--;
11683             pieceType[piece] = (ChessSquare) move->to;
11684             counts[move->to]++;
11685           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11686             counts[pieceType[quickBoard[to]]]--;
11687             quickBoard[to] = 0; total--;
11688             move++;
11689             continue;
11690           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11691             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11692             from  = pieceList[piece]; // so this must be King
11693             quickBoard[from] = 0;
11694             pieceList[piece] = to;
11695             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11696             quickBoard[from] = 0; // rook
11697             quickBoard[to] = piece;
11698             to = move->to; piece = move->piece;
11699             goto aftercastle;
11700           }
11701         }
11702         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11703         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11704         quickBoard[from] = 0;
11705       aftercastle:
11706         quickBoard[to] = piece;
11707         pieceList[piece] = to;
11708         cnt++; turn ^= 3;
11709         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11710            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11711            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11712                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11713           ) {
11714             static int lastCounts[EmptySquare+1];
11715             int i;
11716             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11717             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11718         } else stretch = 0;
11719         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11720         move++;
11721     } while(1);
11722 }
11723
11724 void
11725 InitSearch ()
11726 {
11727     int r, f;
11728     flipSearch = FALSE;
11729     CopyBoard(soughtBoard, boards[currentMove]);
11730     soughtTotal = MakePieceList(soughtBoard, maxSought);
11731     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11732     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11733     CopyBoard(reverseBoard, boards[currentMove]);
11734     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11735         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11736         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11737         reverseBoard[r][f] = piece;
11738     }
11739     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11740     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11741     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11742                  || (boards[currentMove][CASTLING][2] == NoRights || 
11743                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11744                  && (boards[currentMove][CASTLING][5] == NoRights || 
11745                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11746       ) {
11747         flipSearch = TRUE;
11748         CopyBoard(flipBoard, soughtBoard);
11749         CopyBoard(rotateBoard, reverseBoard);
11750         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11751             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11752             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11753         }
11754     }
11755     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11756     if(appData.searchMode >= 5) {
11757         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11758         MakePieceList(soughtBoard, minSought);
11759         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11760     }
11761     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11762         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11763 }
11764
11765 GameInfo dummyInfo;
11766
11767 int
11768 GameContainsPosition (FILE *f, ListGame *lg)
11769 {
11770     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11771     int fromX, fromY, toX, toY;
11772     char promoChar;
11773     static int initDone=FALSE;
11774
11775     // weed out games based on numerical tag comparison
11776     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11777     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11778     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11779     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11780     if(!initDone) {
11781         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11782         initDone = TRUE;
11783     }
11784     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11785     else CopyBoard(boards[scratch], initialPosition); // default start position
11786     if(lg->moves) {
11787         turn = btm + 1;
11788         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11789         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11790     }
11791     if(btm) plyNr++;
11792     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11793     fseek(f, lg->offset, 0);
11794     yynewfile(f);
11795     while(1) {
11796         yyboardindex = scratch;
11797         quickFlag = plyNr+1;
11798         next = Myylex();
11799         quickFlag = 0;
11800         switch(next) {
11801             case PGNTag:
11802                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11803             default:
11804                 continue;
11805
11806             case XBoardGame:
11807             case GNUChessGame:
11808                 if(plyNr) return -1; // after we have seen moves, this is for new game
11809               continue;
11810
11811             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11812             case ImpossibleMove:
11813             case WhiteWins: // game ends here with these four
11814             case BlackWins:
11815             case GameIsDrawn:
11816             case GameUnfinished:
11817                 return -1;
11818
11819             case IllegalMove:
11820                 if(appData.testLegality) return -1;
11821             case WhiteCapturesEnPassant:
11822             case BlackCapturesEnPassant:
11823             case WhitePromotion:
11824             case BlackPromotion:
11825             case WhiteNonPromotion:
11826             case BlackNonPromotion:
11827             case NormalMove:
11828             case WhiteKingSideCastle:
11829             case WhiteQueenSideCastle:
11830             case BlackKingSideCastle:
11831             case BlackQueenSideCastle:
11832             case WhiteKingSideCastleWild:
11833             case WhiteQueenSideCastleWild:
11834             case BlackKingSideCastleWild:
11835             case BlackQueenSideCastleWild:
11836             case WhiteHSideCastleFR:
11837             case WhiteASideCastleFR:
11838             case BlackHSideCastleFR:
11839             case BlackASideCastleFR:
11840                 fromX = currentMoveString[0] - AAA;
11841                 fromY = currentMoveString[1] - ONE;
11842                 toX = currentMoveString[2] - AAA;
11843                 toY = currentMoveString[3] - ONE;
11844                 promoChar = currentMoveString[4];
11845                 break;
11846             case WhiteDrop:
11847             case BlackDrop:
11848                 fromX = next == WhiteDrop ?
11849                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11850                   (int) CharToPiece(ToLower(currentMoveString[0]));
11851                 fromY = DROP_RANK;
11852                 toX = currentMoveString[2] - AAA;
11853                 toY = currentMoveString[3] - ONE;
11854                 promoChar = 0;
11855                 break;
11856         }
11857         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11858         plyNr++;
11859         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11860         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11861         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11862         if(appData.findMirror) {
11863             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11864             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11865         }
11866     }
11867 }
11868
11869 /* Load the nth game from open file f */
11870 int
11871 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11872 {
11873     ChessMove cm;
11874     char buf[MSG_SIZ];
11875     int gn = gameNumber;
11876     ListGame *lg = NULL;
11877     int numPGNTags = 0;
11878     int err, pos = -1;
11879     GameMode oldGameMode;
11880     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11881
11882     if (appData.debugMode)
11883         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11884
11885     if (gameMode == Training )
11886         SetTrainingModeOff();
11887
11888     oldGameMode = gameMode;
11889     if (gameMode != BeginningOfGame) {
11890       Reset(FALSE, TRUE);
11891     }
11892
11893     gameFileFP = f;
11894     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11895         fclose(lastLoadGameFP);
11896     }
11897
11898     if (useList) {
11899         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11900
11901         if (lg) {
11902             fseek(f, lg->offset, 0);
11903             GameListHighlight(gameNumber);
11904             pos = lg->position;
11905             gn = 1;
11906         }
11907         else {
11908             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11909               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11910             else
11911             DisplayError(_("Game number out of range"), 0);
11912             return FALSE;
11913         }
11914     } else {
11915         GameListDestroy();
11916         if (fseek(f, 0, 0) == -1) {
11917             if (f == lastLoadGameFP ?
11918                 gameNumber == lastLoadGameNumber + 1 :
11919                 gameNumber == 1) {
11920                 gn = 1;
11921             } else {
11922                 DisplayError(_("Can't seek on game file"), 0);
11923                 return FALSE;
11924             }
11925         }
11926     }
11927     lastLoadGameFP = f;
11928     lastLoadGameNumber = gameNumber;
11929     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11930     lastLoadGameUseList = useList;
11931
11932     yynewfile(f);
11933
11934     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11935       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11936                 lg->gameInfo.black);
11937             DisplayTitle(buf);
11938     } else if (*title != NULLCHAR) {
11939         if (gameNumber > 1) {
11940           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11941             DisplayTitle(buf);
11942         } else {
11943             DisplayTitle(title);
11944         }
11945     }
11946
11947     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11948         gameMode = PlayFromGameFile;
11949         ModeHighlight();
11950     }
11951
11952     currentMove = forwardMostMove = backwardMostMove = 0;
11953     CopyBoard(boards[0], initialPosition);
11954     StopClocks();
11955
11956     /*
11957      * Skip the first gn-1 games in the file.
11958      * Also skip over anything that precedes an identifiable
11959      * start of game marker, to avoid being confused by
11960      * garbage at the start of the file.  Currently
11961      * recognized start of game markers are the move number "1",
11962      * the pattern "gnuchess .* game", the pattern
11963      * "^[#;%] [^ ]* game file", and a PGN tag block.
11964      * A game that starts with one of the latter two patterns
11965      * will also have a move number 1, possibly
11966      * following a position diagram.
11967      * 5-4-02: Let's try being more lenient and allowing a game to
11968      * start with an unnumbered move.  Does that break anything?
11969      */
11970     cm = lastLoadGameStart = EndOfFile;
11971     while (gn > 0) {
11972         yyboardindex = forwardMostMove;
11973         cm = (ChessMove) Myylex();
11974         switch (cm) {
11975           case EndOfFile:
11976             if (cmailMsgLoaded) {
11977                 nCmailGames = CMAIL_MAX_GAMES - gn;
11978             } else {
11979                 Reset(TRUE, TRUE);
11980                 DisplayError(_("Game not found in file"), 0);
11981             }
11982             return FALSE;
11983
11984           case GNUChessGame:
11985           case XBoardGame:
11986             gn--;
11987             lastLoadGameStart = cm;
11988             break;
11989
11990           case MoveNumberOne:
11991             switch (lastLoadGameStart) {
11992               case GNUChessGame:
11993               case XBoardGame:
11994               case PGNTag:
11995                 break;
11996               case MoveNumberOne:
11997               case EndOfFile:
11998                 gn--;           /* count this game */
11999                 lastLoadGameStart = cm;
12000                 break;
12001               default:
12002                 /* impossible */
12003                 break;
12004             }
12005             break;
12006
12007           case PGNTag:
12008             switch (lastLoadGameStart) {
12009               case GNUChessGame:
12010               case PGNTag:
12011               case MoveNumberOne:
12012               case EndOfFile:
12013                 gn--;           /* count this game */
12014                 lastLoadGameStart = cm;
12015                 break;
12016               case XBoardGame:
12017                 lastLoadGameStart = cm; /* game counted already */
12018                 break;
12019               default:
12020                 /* impossible */
12021                 break;
12022             }
12023             if (gn > 0) {
12024                 do {
12025                     yyboardindex = forwardMostMove;
12026                     cm = (ChessMove) Myylex();
12027                 } while (cm == PGNTag || cm == Comment);
12028             }
12029             break;
12030
12031           case WhiteWins:
12032           case BlackWins:
12033           case GameIsDrawn:
12034             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12035                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12036                     != CMAIL_OLD_RESULT) {
12037                     nCmailResults ++ ;
12038                     cmailResult[  CMAIL_MAX_GAMES
12039                                 - gn - 1] = CMAIL_OLD_RESULT;
12040                 }
12041             }
12042             break;
12043
12044           case NormalMove:
12045             /* Only a NormalMove can be at the start of a game
12046              * without a position diagram. */
12047             if (lastLoadGameStart == EndOfFile ) {
12048               gn--;
12049               lastLoadGameStart = MoveNumberOne;
12050             }
12051             break;
12052
12053           default:
12054             break;
12055         }
12056     }
12057
12058     if (appData.debugMode)
12059       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12060
12061     if (cm == XBoardGame) {
12062         /* Skip any header junk before position diagram and/or move 1 */
12063         for (;;) {
12064             yyboardindex = forwardMostMove;
12065             cm = (ChessMove) Myylex();
12066
12067             if (cm == EndOfFile ||
12068                 cm == GNUChessGame || cm == XBoardGame) {
12069                 /* Empty game; pretend end-of-file and handle later */
12070                 cm = EndOfFile;
12071                 break;
12072             }
12073
12074             if (cm == MoveNumberOne || cm == PositionDiagram ||
12075                 cm == PGNTag || cm == Comment)
12076               break;
12077         }
12078     } else if (cm == GNUChessGame) {
12079         if (gameInfo.event != NULL) {
12080             free(gameInfo.event);
12081         }
12082         gameInfo.event = StrSave(yy_text);
12083     }
12084
12085     startedFromSetupPosition = FALSE;
12086     while (cm == PGNTag) {
12087         if (appData.debugMode)
12088           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12089         err = ParsePGNTag(yy_text, &gameInfo);
12090         if (!err) numPGNTags++;
12091
12092         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12093         if(gameInfo.variant != oldVariant) {
12094             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12095             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12096             InitPosition(TRUE);
12097             oldVariant = gameInfo.variant;
12098             if (appData.debugMode)
12099               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12100         }
12101
12102
12103         if (gameInfo.fen != NULL) {
12104           Board initial_position;
12105           startedFromSetupPosition = TRUE;
12106           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12107             Reset(TRUE, TRUE);
12108             DisplayError(_("Bad FEN position in file"), 0);
12109             return FALSE;
12110           }
12111           CopyBoard(boards[0], initial_position);
12112           if (blackPlaysFirst) {
12113             currentMove = forwardMostMove = backwardMostMove = 1;
12114             CopyBoard(boards[1], initial_position);
12115             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12116             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12117             timeRemaining[0][1] = whiteTimeRemaining;
12118             timeRemaining[1][1] = blackTimeRemaining;
12119             if (commentList[0] != NULL) {
12120               commentList[1] = commentList[0];
12121               commentList[0] = NULL;
12122             }
12123           } else {
12124             currentMove = forwardMostMove = backwardMostMove = 0;
12125           }
12126           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12127           {   int i;
12128               initialRulePlies = FENrulePlies;
12129               for( i=0; i< nrCastlingRights; i++ )
12130                   initialRights[i] = initial_position[CASTLING][i];
12131           }
12132           yyboardindex = forwardMostMove;
12133           free(gameInfo.fen);
12134           gameInfo.fen = NULL;
12135         }
12136
12137         yyboardindex = forwardMostMove;
12138         cm = (ChessMove) Myylex();
12139
12140         /* Handle comments interspersed among the tags */
12141         while (cm == Comment) {
12142             char *p;
12143             if (appData.debugMode)
12144               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12145             p = yy_text;
12146             AppendComment(currentMove, p, FALSE);
12147             yyboardindex = forwardMostMove;
12148             cm = (ChessMove) Myylex();
12149         }
12150     }
12151
12152     /* don't rely on existence of Event tag since if game was
12153      * pasted from clipboard the Event tag may not exist
12154      */
12155     if (numPGNTags > 0){
12156         char *tags;
12157         if (gameInfo.variant == VariantNormal) {
12158           VariantClass v = StringToVariant(gameInfo.event);
12159           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12160           if(v < VariantShogi) gameInfo.variant = v;
12161         }
12162         if (!matchMode) {
12163           if( appData.autoDisplayTags ) {
12164             tags = PGNTags(&gameInfo);
12165             TagsPopUp(tags, CmailMsg());
12166             free(tags);
12167           }
12168         }
12169     } else {
12170         /* Make something up, but don't display it now */
12171         SetGameInfo();
12172         TagsPopDown();
12173     }
12174
12175     if (cm == PositionDiagram) {
12176         int i, j;
12177         char *p;
12178         Board initial_position;
12179
12180         if (appData.debugMode)
12181           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12182
12183         if (!startedFromSetupPosition) {
12184             p = yy_text;
12185             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12186               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12187                 switch (*p) {
12188                   case '{':
12189                   case '[':
12190                   case '-':
12191                   case ' ':
12192                   case '\t':
12193                   case '\n':
12194                   case '\r':
12195                     break;
12196                   default:
12197                     initial_position[i][j++] = CharToPiece(*p);
12198                     break;
12199                 }
12200             while (*p == ' ' || *p == '\t' ||
12201                    *p == '\n' || *p == '\r') p++;
12202
12203             if (strncmp(p, "black", strlen("black"))==0)
12204               blackPlaysFirst = TRUE;
12205             else
12206               blackPlaysFirst = FALSE;
12207             startedFromSetupPosition = TRUE;
12208
12209             CopyBoard(boards[0], initial_position);
12210             if (blackPlaysFirst) {
12211                 currentMove = forwardMostMove = backwardMostMove = 1;
12212                 CopyBoard(boards[1], initial_position);
12213                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12214                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12215                 timeRemaining[0][1] = whiteTimeRemaining;
12216                 timeRemaining[1][1] = blackTimeRemaining;
12217                 if (commentList[0] != NULL) {
12218                     commentList[1] = commentList[0];
12219                     commentList[0] = NULL;
12220                 }
12221             } else {
12222                 currentMove = forwardMostMove = backwardMostMove = 0;
12223             }
12224         }
12225         yyboardindex = forwardMostMove;
12226         cm = (ChessMove) Myylex();
12227     }
12228
12229     if (first.pr == NoProc) {
12230         StartChessProgram(&first);
12231     }
12232     InitChessProgram(&first, FALSE);
12233     SendToProgram("force\n", &first);
12234     if (startedFromSetupPosition) {
12235         SendBoard(&first, forwardMostMove);
12236     if (appData.debugMode) {
12237         fprintf(debugFP, "Load Game\n");
12238     }
12239         DisplayBothClocks();
12240     }
12241
12242     /* [HGM] server: flag to write setup moves in broadcast file as one */
12243     loadFlag = appData.suppressLoadMoves;
12244
12245     while (cm == Comment) {
12246         char *p;
12247         if (appData.debugMode)
12248           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12249         p = yy_text;
12250         AppendComment(currentMove, p, FALSE);
12251         yyboardindex = forwardMostMove;
12252         cm = (ChessMove) Myylex();
12253     }
12254
12255     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12256         cm == WhiteWins || cm == BlackWins ||
12257         cm == GameIsDrawn || cm == GameUnfinished) {
12258         DisplayMessage("", _("No moves in game"));
12259         if (cmailMsgLoaded) {
12260             if (appData.debugMode)
12261               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12262             ClearHighlights();
12263             flipView = FALSE;
12264         }
12265         DrawPosition(FALSE, boards[currentMove]);
12266         DisplayBothClocks();
12267         gameMode = EditGame;
12268         ModeHighlight();
12269         gameFileFP = NULL;
12270         cmailOldMove = 0;
12271         return TRUE;
12272     }
12273
12274     // [HGM] PV info: routine tests if comment empty
12275     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12276         DisplayComment(currentMove - 1, commentList[currentMove]);
12277     }
12278     if (!matchMode && appData.timeDelay != 0)
12279       DrawPosition(FALSE, boards[currentMove]);
12280
12281     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12282       programStats.ok_to_send = 1;
12283     }
12284
12285     /* if the first token after the PGN tags is a move
12286      * and not move number 1, retrieve it from the parser
12287      */
12288     if (cm != MoveNumberOne)
12289         LoadGameOneMove(cm);
12290
12291     /* load the remaining moves from the file */
12292     while (LoadGameOneMove(EndOfFile)) {
12293       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12294       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12295     }
12296
12297     /* rewind to the start of the game */
12298     currentMove = backwardMostMove;
12299
12300     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12301
12302     if (oldGameMode == AnalyzeFile ||
12303         oldGameMode == AnalyzeMode) {
12304       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12305       keepInfo = 1;
12306       AnalyzeFileEvent();
12307       keepInfo = 0;
12308     }
12309
12310     if (!matchMode && pos > 0) {
12311         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12312     } else
12313     if (matchMode || appData.timeDelay == 0) {
12314       ToEndEvent();
12315     } else if (appData.timeDelay > 0) {
12316       AutoPlayGameLoop();
12317     }
12318
12319     if (appData.debugMode)
12320         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12321
12322     loadFlag = 0; /* [HGM] true game starts */
12323     return TRUE;
12324 }
12325
12326 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12327 int
12328 ReloadPosition (int offset)
12329 {
12330     int positionNumber = lastLoadPositionNumber + offset;
12331     if (lastLoadPositionFP == NULL) {
12332         DisplayError(_("No position has been loaded yet"), 0);
12333         return FALSE;
12334     }
12335     if (positionNumber <= 0) {
12336         DisplayError(_("Can't back up any further"), 0);
12337         return FALSE;
12338     }
12339     return LoadPosition(lastLoadPositionFP, positionNumber,
12340                         lastLoadPositionTitle);
12341 }
12342
12343 /* Load the nth position from the given file */
12344 int
12345 LoadPositionFromFile (char *filename, int n, char *title)
12346 {
12347     FILE *f;
12348     char buf[MSG_SIZ];
12349
12350     if (strcmp(filename, "-") == 0) {
12351         return LoadPosition(stdin, n, "stdin");
12352     } else {
12353         f = fopen(filename, "rb");
12354         if (f == NULL) {
12355             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12356             DisplayError(buf, errno);
12357             return FALSE;
12358         } else {
12359             return LoadPosition(f, n, title);
12360         }
12361     }
12362 }
12363
12364 /* Load the nth position from the given open file, and close it */
12365 int
12366 LoadPosition (FILE *f, int positionNumber, char *title)
12367 {
12368     char *p, line[MSG_SIZ];
12369     Board initial_position;
12370     int i, j, fenMode, pn;
12371
12372     if (gameMode == Training )
12373         SetTrainingModeOff();
12374
12375     if (gameMode != BeginningOfGame) {
12376         Reset(FALSE, TRUE);
12377     }
12378     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12379         fclose(lastLoadPositionFP);
12380     }
12381     if (positionNumber == 0) positionNumber = 1;
12382     lastLoadPositionFP = f;
12383     lastLoadPositionNumber = positionNumber;
12384     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12385     if (first.pr == NoProc && !appData.noChessProgram) {
12386       StartChessProgram(&first);
12387       InitChessProgram(&first, FALSE);
12388     }
12389     pn = positionNumber;
12390     if (positionNumber < 0) {
12391         /* Negative position number means to seek to that byte offset */
12392         if (fseek(f, -positionNumber, 0) == -1) {
12393             DisplayError(_("Can't seek on position file"), 0);
12394             return FALSE;
12395         };
12396         pn = 1;
12397     } else {
12398         if (fseek(f, 0, 0) == -1) {
12399             if (f == lastLoadPositionFP ?
12400                 positionNumber == lastLoadPositionNumber + 1 :
12401                 positionNumber == 1) {
12402                 pn = 1;
12403             } else {
12404                 DisplayError(_("Can't seek on position file"), 0);
12405                 return FALSE;
12406             }
12407         }
12408     }
12409     /* See if this file is FEN or old-style xboard */
12410     if (fgets(line, MSG_SIZ, f) == NULL) {
12411         DisplayError(_("Position not found in file"), 0);
12412         return FALSE;
12413     }
12414     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12415     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12416
12417     if (pn >= 2) {
12418         if (fenMode || line[0] == '#') pn--;
12419         while (pn > 0) {
12420             /* skip positions before number pn */
12421             if (fgets(line, MSG_SIZ, f) == NULL) {
12422                 Reset(TRUE, TRUE);
12423                 DisplayError(_("Position not found in file"), 0);
12424                 return FALSE;
12425             }
12426             if (fenMode || line[0] == '#') pn--;
12427         }
12428     }
12429
12430     if (fenMode) {
12431         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12432             DisplayError(_("Bad FEN position in file"), 0);
12433             return FALSE;
12434         }
12435     } else {
12436         (void) fgets(line, MSG_SIZ, f);
12437         (void) fgets(line, MSG_SIZ, f);
12438
12439         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12440             (void) fgets(line, MSG_SIZ, f);
12441             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12442                 if (*p == ' ')
12443                   continue;
12444                 initial_position[i][j++] = CharToPiece(*p);
12445             }
12446         }
12447
12448         blackPlaysFirst = FALSE;
12449         if (!feof(f)) {
12450             (void) fgets(line, MSG_SIZ, f);
12451             if (strncmp(line, "black", strlen("black"))==0)
12452               blackPlaysFirst = TRUE;
12453         }
12454     }
12455     startedFromSetupPosition = TRUE;
12456
12457     CopyBoard(boards[0], initial_position);
12458     if (blackPlaysFirst) {
12459         currentMove = forwardMostMove = backwardMostMove = 1;
12460         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12461         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12462         CopyBoard(boards[1], initial_position);
12463         DisplayMessage("", _("Black to play"));
12464     } else {
12465         currentMove = forwardMostMove = backwardMostMove = 0;
12466         DisplayMessage("", _("White to play"));
12467     }
12468     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12469     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12470         SendToProgram("force\n", &first);
12471         SendBoard(&first, forwardMostMove);
12472     }
12473     if (appData.debugMode) {
12474 int i, j;
12475   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12476   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12477         fprintf(debugFP, "Load Position\n");
12478     }
12479
12480     if (positionNumber > 1) {
12481       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12482         DisplayTitle(line);
12483     } else {
12484         DisplayTitle(title);
12485     }
12486     gameMode = EditGame;
12487     ModeHighlight();
12488     ResetClocks();
12489     timeRemaining[0][1] = whiteTimeRemaining;
12490     timeRemaining[1][1] = blackTimeRemaining;
12491     DrawPosition(FALSE, boards[currentMove]);
12492
12493     return TRUE;
12494 }
12495
12496
12497 void
12498 CopyPlayerNameIntoFileName (char **dest, char *src)
12499 {
12500     while (*src != NULLCHAR && *src != ',') {
12501         if (*src == ' ') {
12502             *(*dest)++ = '_';
12503             src++;
12504         } else {
12505             *(*dest)++ = *src++;
12506         }
12507     }
12508 }
12509
12510 char *
12511 DefaultFileName (char *ext)
12512 {
12513     static char def[MSG_SIZ];
12514     char *p;
12515
12516     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12517         p = def;
12518         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12519         *p++ = '-';
12520         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12521         *p++ = '.';
12522         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12523     } else {
12524         def[0] = NULLCHAR;
12525     }
12526     return def;
12527 }
12528
12529 /* Save the current game to the given file */
12530 int
12531 SaveGameToFile (char *filename, int append)
12532 {
12533     FILE *f;
12534     char buf[MSG_SIZ];
12535     int result, i, t,tot=0;
12536
12537     if (strcmp(filename, "-") == 0) {
12538         return SaveGame(stdout, 0, NULL);
12539     } else {
12540         for(i=0; i<10; i++) { // upto 10 tries
12541              f = fopen(filename, append ? "a" : "w");
12542              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12543              if(f || errno != 13) break;
12544              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12545              tot += t;
12546         }
12547         if (f == NULL) {
12548             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12549             DisplayError(buf, errno);
12550             return FALSE;
12551         } else {
12552             safeStrCpy(buf, lastMsg, MSG_SIZ);
12553             DisplayMessage(_("Waiting for access to save file"), "");
12554             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12555             DisplayMessage(_("Saving game"), "");
12556             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12557             result = SaveGame(f, 0, NULL);
12558             DisplayMessage(buf, "");
12559             return result;
12560         }
12561     }
12562 }
12563
12564 char *
12565 SavePart (char *str)
12566 {
12567     static char buf[MSG_SIZ];
12568     char *p;
12569
12570     p = strchr(str, ' ');
12571     if (p == NULL) return str;
12572     strncpy(buf, str, p - str);
12573     buf[p - str] = NULLCHAR;
12574     return buf;
12575 }
12576
12577 #define PGN_MAX_LINE 75
12578
12579 #define PGN_SIDE_WHITE  0
12580 #define PGN_SIDE_BLACK  1
12581
12582 static int
12583 FindFirstMoveOutOfBook (int side)
12584 {
12585     int result = -1;
12586
12587     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12588         int index = backwardMostMove;
12589         int has_book_hit = 0;
12590
12591         if( (index % 2) != side ) {
12592             index++;
12593         }
12594
12595         while( index < forwardMostMove ) {
12596             /* Check to see if engine is in book */
12597             int depth = pvInfoList[index].depth;
12598             int score = pvInfoList[index].score;
12599             int in_book = 0;
12600
12601             if( depth <= 2 ) {
12602                 in_book = 1;
12603             }
12604             else if( score == 0 && depth == 63 ) {
12605                 in_book = 1; /* Zappa */
12606             }
12607             else if( score == 2 && depth == 99 ) {
12608                 in_book = 1; /* Abrok */
12609             }
12610
12611             has_book_hit += in_book;
12612
12613             if( ! in_book ) {
12614                 result = index;
12615
12616                 break;
12617             }
12618
12619             index += 2;
12620         }
12621     }
12622
12623     return result;
12624 }
12625
12626 void
12627 GetOutOfBookInfo (char * buf)
12628 {
12629     int oob[2];
12630     int i;
12631     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12632
12633     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12634     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12635
12636     *buf = '\0';
12637
12638     if( oob[0] >= 0 || oob[1] >= 0 ) {
12639         for( i=0; i<2; i++ ) {
12640             int idx = oob[i];
12641
12642             if( idx >= 0 ) {
12643                 if( i > 0 && oob[0] >= 0 ) {
12644                     strcat( buf, "   " );
12645                 }
12646
12647                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12648                 sprintf( buf+strlen(buf), "%s%.2f",
12649                     pvInfoList[idx].score >= 0 ? "+" : "",
12650                     pvInfoList[idx].score / 100.0 );
12651             }
12652         }
12653     }
12654 }
12655
12656 /* Save game in PGN style and close the file */
12657 int
12658 SaveGamePGN (FILE *f)
12659 {
12660     int i, offset, linelen, newblock;
12661 //    char *movetext;
12662     char numtext[32];
12663     int movelen, numlen, blank;
12664     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12665
12666     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12667
12668     PrintPGNTags(f, &gameInfo);
12669
12670     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12671
12672     if (backwardMostMove > 0 || startedFromSetupPosition) {
12673         char *fen = PositionToFEN(backwardMostMove, NULL);
12674         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12675         fprintf(f, "\n{--------------\n");
12676         PrintPosition(f, backwardMostMove);
12677         fprintf(f, "--------------}\n");
12678         free(fen);
12679     }
12680     else {
12681         /* [AS] Out of book annotation */
12682         if( appData.saveOutOfBookInfo ) {
12683             char buf[64];
12684
12685             GetOutOfBookInfo( buf );
12686
12687             if( buf[0] != '\0' ) {
12688                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12689             }
12690         }
12691
12692         fprintf(f, "\n");
12693     }
12694
12695     i = backwardMostMove;
12696     linelen = 0;
12697     newblock = TRUE;
12698
12699     while (i < forwardMostMove) {
12700         /* Print comments preceding this move */
12701         if (commentList[i] != NULL) {
12702             if (linelen > 0) fprintf(f, "\n");
12703             fprintf(f, "%s", commentList[i]);
12704             linelen = 0;
12705             newblock = TRUE;
12706         }
12707
12708         /* Format move number */
12709         if ((i % 2) == 0)
12710           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12711         else
12712           if (newblock)
12713             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12714           else
12715             numtext[0] = NULLCHAR;
12716
12717         numlen = strlen(numtext);
12718         newblock = FALSE;
12719
12720         /* Print move number */
12721         blank = linelen > 0 && numlen > 0;
12722         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12723             fprintf(f, "\n");
12724             linelen = 0;
12725             blank = 0;
12726         }
12727         if (blank) {
12728             fprintf(f, " ");
12729             linelen++;
12730         }
12731         fprintf(f, "%s", numtext);
12732         linelen += numlen;
12733
12734         /* Get move */
12735         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12736         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12737
12738         /* Print move */
12739         blank = linelen > 0 && movelen > 0;
12740         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12741             fprintf(f, "\n");
12742             linelen = 0;
12743             blank = 0;
12744         }
12745         if (blank) {
12746             fprintf(f, " ");
12747             linelen++;
12748         }
12749         fprintf(f, "%s", move_buffer);
12750         linelen += movelen;
12751
12752         /* [AS] Add PV info if present */
12753         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12754             /* [HGM] add time */
12755             char buf[MSG_SIZ]; int seconds;
12756
12757             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12758
12759             if( seconds <= 0)
12760               buf[0] = 0;
12761             else
12762               if( seconds < 30 )
12763                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12764               else
12765                 {
12766                   seconds = (seconds + 4)/10; // round to full seconds
12767                   if( seconds < 60 )
12768                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12769                   else
12770                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12771                 }
12772
12773             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12774                       pvInfoList[i].score >= 0 ? "+" : "",
12775                       pvInfoList[i].score / 100.0,
12776                       pvInfoList[i].depth,
12777                       buf );
12778
12779             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12780
12781             /* Print score/depth */
12782             blank = linelen > 0 && movelen > 0;
12783             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12784                 fprintf(f, "\n");
12785                 linelen = 0;
12786                 blank = 0;
12787             }
12788             if (blank) {
12789                 fprintf(f, " ");
12790                 linelen++;
12791             }
12792             fprintf(f, "%s", move_buffer);
12793             linelen += movelen;
12794         }
12795
12796         i++;
12797     }
12798
12799     /* Start a new line */
12800     if (linelen > 0) fprintf(f, "\n");
12801
12802     /* Print comments after last move */
12803     if (commentList[i] != NULL) {
12804         fprintf(f, "%s\n", commentList[i]);
12805     }
12806
12807     /* Print result */
12808     if (gameInfo.resultDetails != NULL &&
12809         gameInfo.resultDetails[0] != NULLCHAR) {
12810         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12811                 PGNResult(gameInfo.result));
12812     } else {
12813         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12814     }
12815
12816     fclose(f);
12817     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12818     return TRUE;
12819 }
12820
12821 /* Save game in old style and close the file */
12822 int
12823 SaveGameOldStyle (FILE *f)
12824 {
12825     int i, offset;
12826     time_t tm;
12827
12828     tm = time((time_t *) NULL);
12829
12830     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12831     PrintOpponents(f);
12832
12833     if (backwardMostMove > 0 || startedFromSetupPosition) {
12834         fprintf(f, "\n[--------------\n");
12835         PrintPosition(f, backwardMostMove);
12836         fprintf(f, "--------------]\n");
12837     } else {
12838         fprintf(f, "\n");
12839     }
12840
12841     i = backwardMostMove;
12842     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12843
12844     while (i < forwardMostMove) {
12845         if (commentList[i] != NULL) {
12846             fprintf(f, "[%s]\n", commentList[i]);
12847         }
12848
12849         if ((i % 2) == 1) {
12850             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12851             i++;
12852         } else {
12853             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12854             i++;
12855             if (commentList[i] != NULL) {
12856                 fprintf(f, "\n");
12857                 continue;
12858             }
12859             if (i >= forwardMostMove) {
12860                 fprintf(f, "\n");
12861                 break;
12862             }
12863             fprintf(f, "%s\n", parseList[i]);
12864             i++;
12865         }
12866     }
12867
12868     if (commentList[i] != NULL) {
12869         fprintf(f, "[%s]\n", commentList[i]);
12870     }
12871
12872     /* This isn't really the old style, but it's close enough */
12873     if (gameInfo.resultDetails != NULL &&
12874         gameInfo.resultDetails[0] != NULLCHAR) {
12875         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12876                 gameInfo.resultDetails);
12877     } else {
12878         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12879     }
12880
12881     fclose(f);
12882     return TRUE;
12883 }
12884
12885 /* Save the current game to open file f and close the file */
12886 int
12887 SaveGame (FILE *f, int dummy, char *dummy2)
12888 {
12889     if (gameMode == EditPosition) EditPositionDone(TRUE);
12890     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12891     if (appData.oldSaveStyle)
12892       return SaveGameOldStyle(f);
12893     else
12894       return SaveGamePGN(f);
12895 }
12896
12897 /* Save the current position to the given file */
12898 int
12899 SavePositionToFile (char *filename)
12900 {
12901     FILE *f;
12902     char buf[MSG_SIZ];
12903
12904     if (strcmp(filename, "-") == 0) {
12905         return SavePosition(stdout, 0, NULL);
12906     } else {
12907         f = fopen(filename, "a");
12908         if (f == NULL) {
12909             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12910             DisplayError(buf, errno);
12911             return FALSE;
12912         } else {
12913             safeStrCpy(buf, lastMsg, MSG_SIZ);
12914             DisplayMessage(_("Waiting for access to save file"), "");
12915             flock(fileno(f), LOCK_EX); // [HGM] lock
12916             DisplayMessage(_("Saving position"), "");
12917             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12918             SavePosition(f, 0, NULL);
12919             DisplayMessage(buf, "");
12920             return TRUE;
12921         }
12922     }
12923 }
12924
12925 /* Save the current position to the given open file and close the file */
12926 int
12927 SavePosition (FILE *f, int dummy, char *dummy2)
12928 {
12929     time_t tm;
12930     char *fen;
12931
12932     if (gameMode == EditPosition) EditPositionDone(TRUE);
12933     if (appData.oldSaveStyle) {
12934         tm = time((time_t *) NULL);
12935
12936         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12937         PrintOpponents(f);
12938         fprintf(f, "[--------------\n");
12939         PrintPosition(f, currentMove);
12940         fprintf(f, "--------------]\n");
12941     } else {
12942         fen = PositionToFEN(currentMove, NULL);
12943         fprintf(f, "%s\n", fen);
12944         free(fen);
12945     }
12946     fclose(f);
12947     return TRUE;
12948 }
12949
12950 void
12951 ReloadCmailMsgEvent (int unregister)
12952 {
12953 #if !WIN32
12954     static char *inFilename = NULL;
12955     static char *outFilename;
12956     int i;
12957     struct stat inbuf, outbuf;
12958     int status;
12959
12960     /* Any registered moves are unregistered if unregister is set, */
12961     /* i.e. invoked by the signal handler */
12962     if (unregister) {
12963         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12964             cmailMoveRegistered[i] = FALSE;
12965             if (cmailCommentList[i] != NULL) {
12966                 free(cmailCommentList[i]);
12967                 cmailCommentList[i] = NULL;
12968             }
12969         }
12970         nCmailMovesRegistered = 0;
12971     }
12972
12973     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12974         cmailResult[i] = CMAIL_NOT_RESULT;
12975     }
12976     nCmailResults = 0;
12977
12978     if (inFilename == NULL) {
12979         /* Because the filenames are static they only get malloced once  */
12980         /* and they never get freed                                      */
12981         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12982         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12983
12984         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12985         sprintf(outFilename, "%s.out", appData.cmailGameName);
12986     }
12987
12988     status = stat(outFilename, &outbuf);
12989     if (status < 0) {
12990         cmailMailedMove = FALSE;
12991     } else {
12992         status = stat(inFilename, &inbuf);
12993         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12994     }
12995
12996     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12997        counts the games, notes how each one terminated, etc.
12998
12999        It would be nice to remove this kludge and instead gather all
13000        the information while building the game list.  (And to keep it
13001        in the game list nodes instead of having a bunch of fixed-size
13002        parallel arrays.)  Note this will require getting each game's
13003        termination from the PGN tags, as the game list builder does
13004        not process the game moves.  --mann
13005        */
13006     cmailMsgLoaded = TRUE;
13007     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13008
13009     /* Load first game in the file or popup game menu */
13010     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13011
13012 #endif /* !WIN32 */
13013     return;
13014 }
13015
13016 int
13017 RegisterMove ()
13018 {
13019     FILE *f;
13020     char string[MSG_SIZ];
13021
13022     if (   cmailMailedMove
13023         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13024         return TRUE;            /* Allow free viewing  */
13025     }
13026
13027     /* Unregister move to ensure that we don't leave RegisterMove        */
13028     /* with the move registered when the conditions for registering no   */
13029     /* longer hold                                                       */
13030     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13031         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13032         nCmailMovesRegistered --;
13033
13034         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13035           {
13036               free(cmailCommentList[lastLoadGameNumber - 1]);
13037               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13038           }
13039     }
13040
13041     if (cmailOldMove == -1) {
13042         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13043         return FALSE;
13044     }
13045
13046     if (currentMove > cmailOldMove + 1) {
13047         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13048         return FALSE;
13049     }
13050
13051     if (currentMove < cmailOldMove) {
13052         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13053         return FALSE;
13054     }
13055
13056     if (forwardMostMove > currentMove) {
13057         /* Silently truncate extra moves */
13058         TruncateGame();
13059     }
13060
13061     if (   (currentMove == cmailOldMove + 1)
13062         || (   (currentMove == cmailOldMove)
13063             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13064                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13065         if (gameInfo.result != GameUnfinished) {
13066             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13067         }
13068
13069         if (commentList[currentMove] != NULL) {
13070             cmailCommentList[lastLoadGameNumber - 1]
13071               = StrSave(commentList[currentMove]);
13072         }
13073         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13074
13075         if (appData.debugMode)
13076           fprintf(debugFP, "Saving %s for game %d\n",
13077                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13078
13079         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13080
13081         f = fopen(string, "w");
13082         if (appData.oldSaveStyle) {
13083             SaveGameOldStyle(f); /* also closes the file */
13084
13085             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13086             f = fopen(string, "w");
13087             SavePosition(f, 0, NULL); /* also closes the file */
13088         } else {
13089             fprintf(f, "{--------------\n");
13090             PrintPosition(f, currentMove);
13091             fprintf(f, "--------------}\n\n");
13092
13093             SaveGame(f, 0, NULL); /* also closes the file*/
13094         }
13095
13096         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13097         nCmailMovesRegistered ++;
13098     } else if (nCmailGames == 1) {
13099         DisplayError(_("You have not made a move yet"), 0);
13100         return FALSE;
13101     }
13102
13103     return TRUE;
13104 }
13105
13106 void
13107 MailMoveEvent ()
13108 {
13109 #if !WIN32
13110     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13111     FILE *commandOutput;
13112     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13113     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13114     int nBuffers;
13115     int i;
13116     int archived;
13117     char *arcDir;
13118
13119     if (! cmailMsgLoaded) {
13120         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13121         return;
13122     }
13123
13124     if (nCmailGames == nCmailResults) {
13125         DisplayError(_("No unfinished games"), 0);
13126         return;
13127     }
13128
13129 #if CMAIL_PROHIBIT_REMAIL
13130     if (cmailMailedMove) {
13131       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);
13132         DisplayError(msg, 0);
13133         return;
13134     }
13135 #endif
13136
13137     if (! (cmailMailedMove || RegisterMove())) return;
13138
13139     if (   cmailMailedMove
13140         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13141       snprintf(string, MSG_SIZ, partCommandString,
13142                appData.debugMode ? " -v" : "", appData.cmailGameName);
13143         commandOutput = popen(string, "r");
13144
13145         if (commandOutput == NULL) {
13146             DisplayError(_("Failed to invoke cmail"), 0);
13147         } else {
13148             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13149                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13150             }
13151             if (nBuffers > 1) {
13152                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13153                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13154                 nBytes = MSG_SIZ - 1;
13155             } else {
13156                 (void) memcpy(msg, buffer, nBytes);
13157             }
13158             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13159
13160             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13161                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13162
13163                 archived = TRUE;
13164                 for (i = 0; i < nCmailGames; i ++) {
13165                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13166                         archived = FALSE;
13167                     }
13168                 }
13169                 if (   archived
13170                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13171                         != NULL)) {
13172                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13173                            arcDir,
13174                            appData.cmailGameName,
13175                            gameInfo.date);
13176                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13177                     cmailMsgLoaded = FALSE;
13178                 }
13179             }
13180
13181             DisplayInformation(msg);
13182             pclose(commandOutput);
13183         }
13184     } else {
13185         if ((*cmailMsg) != '\0') {
13186             DisplayInformation(cmailMsg);
13187         }
13188     }
13189
13190     return;
13191 #endif /* !WIN32 */
13192 }
13193
13194 char *
13195 CmailMsg ()
13196 {
13197 #if WIN32
13198     return NULL;
13199 #else
13200     int  prependComma = 0;
13201     char number[5];
13202     char string[MSG_SIZ];       /* Space for game-list */
13203     int  i;
13204
13205     if (!cmailMsgLoaded) return "";
13206
13207     if (cmailMailedMove) {
13208       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13209     } else {
13210         /* Create a list of games left */
13211       snprintf(string, MSG_SIZ, "[");
13212         for (i = 0; i < nCmailGames; i ++) {
13213             if (! (   cmailMoveRegistered[i]
13214                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13215                 if (prependComma) {
13216                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13217                 } else {
13218                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13219                     prependComma = 1;
13220                 }
13221
13222                 strcat(string, number);
13223             }
13224         }
13225         strcat(string, "]");
13226
13227         if (nCmailMovesRegistered + nCmailResults == 0) {
13228             switch (nCmailGames) {
13229               case 1:
13230                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13231                 break;
13232
13233               case 2:
13234                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13235                 break;
13236
13237               default:
13238                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13239                          nCmailGames);
13240                 break;
13241             }
13242         } else {
13243             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13244               case 1:
13245                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13246                          string);
13247                 break;
13248
13249               case 0:
13250                 if (nCmailResults == nCmailGames) {
13251                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13252                 } else {
13253                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13254                 }
13255                 break;
13256
13257               default:
13258                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13259                          string);
13260             }
13261         }
13262     }
13263     return cmailMsg;
13264 #endif /* WIN32 */
13265 }
13266
13267 void
13268 ResetGameEvent ()
13269 {
13270     if (gameMode == Training)
13271       SetTrainingModeOff();
13272
13273     Reset(TRUE, TRUE);
13274     cmailMsgLoaded = FALSE;
13275     if (appData.icsActive) {
13276       SendToICS(ics_prefix);
13277       SendToICS("refresh\n");
13278     }
13279 }
13280
13281 void
13282 ExitEvent (int status)
13283 {
13284     exiting++;
13285     if (exiting > 2) {
13286       /* Give up on clean exit */
13287       exit(status);
13288     }
13289     if (exiting > 1) {
13290       /* Keep trying for clean exit */
13291       return;
13292     }
13293
13294     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13295
13296     if (telnetISR != NULL) {
13297       RemoveInputSource(telnetISR);
13298     }
13299     if (icsPR != NoProc) {
13300       DestroyChildProcess(icsPR, TRUE);
13301     }
13302
13303     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13304     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13305
13306     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13307     /* make sure this other one finishes before killing it!                  */
13308     if(endingGame) { int count = 0;
13309         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13310         while(endingGame && count++ < 10) DoSleep(1);
13311         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13312     }
13313
13314     /* Kill off chess programs */
13315     if (first.pr != NoProc) {
13316         ExitAnalyzeMode();
13317
13318         DoSleep( appData.delayBeforeQuit );
13319         SendToProgram("quit\n", &first);
13320         DoSleep( appData.delayAfterQuit );
13321         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13322     }
13323     if (second.pr != NoProc) {
13324         DoSleep( appData.delayBeforeQuit );
13325         SendToProgram("quit\n", &second);
13326         DoSleep( appData.delayAfterQuit );
13327         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13328     }
13329     if (first.isr != NULL) {
13330         RemoveInputSource(first.isr);
13331     }
13332     if (second.isr != NULL) {
13333         RemoveInputSource(second.isr);
13334     }
13335
13336     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13337     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13338
13339     ShutDownFrontEnd();
13340     exit(status);
13341 }
13342
13343 void
13344 PauseEvent ()
13345 {
13346     if (appData.debugMode)
13347         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13348     if (pausing) {
13349         pausing = FALSE;
13350         ModeHighlight();
13351         if (gameMode == MachinePlaysWhite ||
13352             gameMode == MachinePlaysBlack) {
13353             StartClocks();
13354         } else {
13355             DisplayBothClocks();
13356         }
13357         if (gameMode == PlayFromGameFile) {
13358             if (appData.timeDelay >= 0)
13359                 AutoPlayGameLoop();
13360         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13361             Reset(FALSE, TRUE);
13362             SendToICS(ics_prefix);
13363             SendToICS("refresh\n");
13364         } else if (currentMove < forwardMostMove) {
13365             ForwardInner(forwardMostMove);
13366         }
13367         pauseExamInvalid = FALSE;
13368     } else {
13369         switch (gameMode) {
13370           default:
13371             return;
13372           case IcsExamining:
13373             pauseExamForwardMostMove = forwardMostMove;
13374             pauseExamInvalid = FALSE;
13375             /* fall through */
13376           case IcsObserving:
13377           case IcsPlayingWhite:
13378           case IcsPlayingBlack:
13379             pausing = TRUE;
13380             ModeHighlight();
13381             return;
13382           case PlayFromGameFile:
13383             (void) StopLoadGameTimer();
13384             pausing = TRUE;
13385             ModeHighlight();
13386             break;
13387           case BeginningOfGame:
13388             if (appData.icsActive) return;
13389             /* else fall through */
13390           case MachinePlaysWhite:
13391           case MachinePlaysBlack:
13392           case TwoMachinesPlay:
13393             if (forwardMostMove == 0)
13394               return;           /* don't pause if no one has moved */
13395             if ((gameMode == MachinePlaysWhite &&
13396                  !WhiteOnMove(forwardMostMove)) ||
13397                 (gameMode == MachinePlaysBlack &&
13398                  WhiteOnMove(forwardMostMove))) {
13399                 StopClocks();
13400             }
13401           case AnalyzeMode:
13402             pausing = TRUE;
13403             ModeHighlight();
13404             break;
13405         }
13406     }
13407 }
13408
13409 void
13410 EditCommentEvent ()
13411 {
13412     char title[MSG_SIZ];
13413
13414     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13415       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13416     } else {
13417       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13418                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13419                parseList[currentMove - 1]);
13420     }
13421
13422     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13423 }
13424
13425
13426 void
13427 EditTagsEvent ()
13428 {
13429     char *tags = PGNTags(&gameInfo);
13430     bookUp = FALSE;
13431     EditTagsPopUp(tags, NULL);
13432     free(tags);
13433 }
13434
13435 void
13436 ToggleSecond ()
13437 {
13438   if(second.analyzing) {
13439     SendToProgram("exit\n", &second);
13440     second.analyzing = FALSE;
13441   } else {
13442     if (second.pr == NoProc) StartChessProgram(&second);
13443     InitChessProgram(&second, FALSE);
13444     FeedMovesToProgram(&second, currentMove);
13445
13446     SendToProgram("analyze\n", &second);
13447     second.analyzing = TRUE;
13448   }
13449 }
13450
13451 void
13452 AnalyzeModeEvent ()
13453 {
13454     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13455     if (appData.noChessProgram || gameMode == AnalyzeMode)
13456       return;
13457
13458     if (gameMode != AnalyzeFile) {
13459         if (!appData.icsEngineAnalyze) {
13460                EditGameEvent();
13461                if (gameMode != EditGame) return;
13462         }
13463         ResurrectChessProgram();
13464         SendToProgram("analyze\n", &first);
13465         first.analyzing = TRUE;
13466         /*first.maybeThinking = TRUE;*/
13467         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13468         EngineOutputPopUp();
13469     }
13470     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13471     pausing = FALSE;
13472     ModeHighlight();
13473     SetGameInfo();
13474
13475     StartAnalysisClock();
13476     GetTimeMark(&lastNodeCountTime);
13477     lastNodeCount = 0;
13478 }
13479
13480 void
13481 AnalyzeFileEvent ()
13482 {
13483     if (appData.noChessProgram || gameMode == AnalyzeFile)
13484       return;
13485
13486     if (gameMode != AnalyzeMode) {
13487         EditGameEvent();
13488         if (gameMode != EditGame) return;
13489         ResurrectChessProgram();
13490         SendToProgram("analyze\n", &first);
13491         first.analyzing = TRUE;
13492         /*first.maybeThinking = TRUE;*/
13493         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13494         EngineOutputPopUp();
13495     }
13496     gameMode = AnalyzeFile;
13497     pausing = FALSE;
13498     ModeHighlight();
13499     SetGameInfo();
13500
13501     StartAnalysisClock();
13502     GetTimeMark(&lastNodeCountTime);
13503     lastNodeCount = 0;
13504     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13505 }
13506
13507 void
13508 MachineWhiteEvent ()
13509 {
13510     char buf[MSG_SIZ];
13511     char *bookHit = NULL;
13512
13513     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13514       return;
13515
13516
13517     if (gameMode == PlayFromGameFile ||
13518         gameMode == TwoMachinesPlay  ||
13519         gameMode == Training         ||
13520         gameMode == AnalyzeMode      ||
13521         gameMode == EndOfGame)
13522         EditGameEvent();
13523
13524     if (gameMode == EditPosition)
13525         EditPositionDone(TRUE);
13526
13527     if (!WhiteOnMove(currentMove)) {
13528         DisplayError(_("It is not White's turn"), 0);
13529         return;
13530     }
13531
13532     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13533       ExitAnalyzeMode();
13534
13535     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13536         gameMode == AnalyzeFile)
13537         TruncateGame();
13538
13539     ResurrectChessProgram();    /* in case it isn't running */
13540     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13541         gameMode = MachinePlaysWhite;
13542         ResetClocks();
13543     } else
13544     gameMode = MachinePlaysWhite;
13545     pausing = FALSE;
13546     ModeHighlight();
13547     SetGameInfo();
13548     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13549     DisplayTitle(buf);
13550     if (first.sendName) {
13551       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13552       SendToProgram(buf, &first);
13553     }
13554     if (first.sendTime) {
13555       if (first.useColors) {
13556         SendToProgram("black\n", &first); /*gnu kludge*/
13557       }
13558       SendTimeRemaining(&first, TRUE);
13559     }
13560     if (first.useColors) {
13561       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13562     }
13563     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13564     SetMachineThinkingEnables();
13565     first.maybeThinking = TRUE;
13566     StartClocks();
13567     firstMove = FALSE;
13568
13569     if (appData.autoFlipView && !flipView) {
13570       flipView = !flipView;
13571       DrawPosition(FALSE, NULL);
13572       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13573     }
13574
13575     if(bookHit) { // [HGM] book: simulate book reply
13576         static char bookMove[MSG_SIZ]; // a bit generous?
13577
13578         programStats.nodes = programStats.depth = programStats.time =
13579         programStats.score = programStats.got_only_move = 0;
13580         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13581
13582         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13583         strcat(bookMove, bookHit);
13584         HandleMachineMove(bookMove, &first);
13585     }
13586 }
13587
13588 void
13589 MachineBlackEvent ()
13590 {
13591   char buf[MSG_SIZ];
13592   char *bookHit = NULL;
13593
13594     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13595         return;
13596
13597
13598     if (gameMode == PlayFromGameFile ||
13599         gameMode == TwoMachinesPlay  ||
13600         gameMode == Training         ||
13601         gameMode == AnalyzeMode      ||
13602         gameMode == EndOfGame)
13603         EditGameEvent();
13604
13605     if (gameMode == EditPosition)
13606         EditPositionDone(TRUE);
13607
13608     if (WhiteOnMove(currentMove)) {
13609         DisplayError(_("It is not Black's turn"), 0);
13610         return;
13611     }
13612
13613     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13614       ExitAnalyzeMode();
13615
13616     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13617         gameMode == AnalyzeFile)
13618         TruncateGame();
13619
13620     ResurrectChessProgram();    /* in case it isn't running */
13621     gameMode = MachinePlaysBlack;
13622     pausing = FALSE;
13623     ModeHighlight();
13624     SetGameInfo();
13625     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13626     DisplayTitle(buf);
13627     if (first.sendName) {
13628       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13629       SendToProgram(buf, &first);
13630     }
13631     if (first.sendTime) {
13632       if (first.useColors) {
13633         SendToProgram("white\n", &first); /*gnu kludge*/
13634       }
13635       SendTimeRemaining(&first, FALSE);
13636     }
13637     if (first.useColors) {
13638       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13639     }
13640     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13641     SetMachineThinkingEnables();
13642     first.maybeThinking = TRUE;
13643     StartClocks();
13644
13645     if (appData.autoFlipView && flipView) {
13646       flipView = !flipView;
13647       DrawPosition(FALSE, NULL);
13648       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13649     }
13650     if(bookHit) { // [HGM] book: simulate book reply
13651         static char bookMove[MSG_SIZ]; // a bit generous?
13652
13653         programStats.nodes = programStats.depth = programStats.time =
13654         programStats.score = programStats.got_only_move = 0;
13655         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13656
13657         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13658         strcat(bookMove, bookHit);
13659         HandleMachineMove(bookMove, &first);
13660     }
13661 }
13662
13663
13664 void
13665 DisplayTwoMachinesTitle ()
13666 {
13667     char buf[MSG_SIZ];
13668     if (appData.matchGames > 0) {
13669         if(appData.tourneyFile[0]) {
13670           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13671                    gameInfo.white, _("vs."), gameInfo.black,
13672                    nextGame+1, appData.matchGames+1,
13673                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13674         } else 
13675         if (first.twoMachinesColor[0] == 'w') {
13676           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13677                    gameInfo.white, _("vs."),  gameInfo.black,
13678                    first.matchWins, second.matchWins,
13679                    matchGame - 1 - (first.matchWins + second.matchWins));
13680         } else {
13681           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13682                    gameInfo.white, _("vs."), gameInfo.black,
13683                    second.matchWins, first.matchWins,
13684                    matchGame - 1 - (first.matchWins + second.matchWins));
13685         }
13686     } else {
13687       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13688     }
13689     DisplayTitle(buf);
13690 }
13691
13692 void
13693 SettingsMenuIfReady ()
13694 {
13695   if (second.lastPing != second.lastPong) {
13696     DisplayMessage("", _("Waiting for second chess program"));
13697     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13698     return;
13699   }
13700   ThawUI();
13701   DisplayMessage("", "");
13702   SettingsPopUp(&second);
13703 }
13704
13705 int
13706 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13707 {
13708     char buf[MSG_SIZ];
13709     if (cps->pr == NoProc) {
13710         StartChessProgram(cps);
13711         if (cps->protocolVersion == 1) {
13712           retry();
13713         } else {
13714           /* kludge: allow timeout for initial "feature" command */
13715           FreezeUI();
13716           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13717           DisplayMessage("", buf);
13718           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13719         }
13720         return 1;
13721     }
13722     return 0;
13723 }
13724
13725 void
13726 TwoMachinesEvent P((void))
13727 {
13728     int i;
13729     char buf[MSG_SIZ];
13730     ChessProgramState *onmove;
13731     char *bookHit = NULL;
13732     static int stalling = 0;
13733     TimeMark now;
13734     long wait;
13735
13736     if (appData.noChessProgram) return;
13737
13738     switch (gameMode) {
13739       case TwoMachinesPlay:
13740         return;
13741       case MachinePlaysWhite:
13742       case MachinePlaysBlack:
13743         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13744             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13745             return;
13746         }
13747         /* fall through */
13748       case BeginningOfGame:
13749       case PlayFromGameFile:
13750       case EndOfGame:
13751         EditGameEvent();
13752         if (gameMode != EditGame) return;
13753         break;
13754       case EditPosition:
13755         EditPositionDone(TRUE);
13756         break;
13757       case AnalyzeMode:
13758       case AnalyzeFile:
13759         ExitAnalyzeMode();
13760         break;
13761       case EditGame:
13762       default:
13763         break;
13764     }
13765
13766 //    forwardMostMove = currentMove;
13767     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13768
13769     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13770
13771     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13772     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13773       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13774       return;
13775     }
13776
13777     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13778         DisplayError("second engine does not play this", 0);
13779         return;
13780     }
13781
13782     if(!stalling) {
13783       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13784       SendToProgram("force\n", &second);
13785       stalling = 1;
13786       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13787       return;
13788     }
13789     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13790     if(appData.matchPause>10000 || appData.matchPause<10)
13791                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13792     wait = SubtractTimeMarks(&now, &pauseStart);
13793     if(wait < appData.matchPause) {
13794         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13795         return;
13796     }
13797     // we are now committed to starting the game
13798     stalling = 0;
13799     DisplayMessage("", "");
13800     if (startedFromSetupPosition) {
13801         SendBoard(&second, backwardMostMove);
13802     if (appData.debugMode) {
13803         fprintf(debugFP, "Two Machines\n");
13804     }
13805     }
13806     for (i = backwardMostMove; i < forwardMostMove; i++) {
13807         SendMoveToProgram(i, &second);
13808     }
13809
13810     gameMode = TwoMachinesPlay;
13811     pausing = FALSE;
13812     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13813     SetGameInfo();
13814     DisplayTwoMachinesTitle();
13815     firstMove = TRUE;
13816     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13817         onmove = &first;
13818     } else {
13819         onmove = &second;
13820     }
13821     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13822     SendToProgram(first.computerString, &first);
13823     if (first.sendName) {
13824       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13825       SendToProgram(buf, &first);
13826     }
13827     SendToProgram(second.computerString, &second);
13828     if (second.sendName) {
13829       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13830       SendToProgram(buf, &second);
13831     }
13832
13833     ResetClocks();
13834     if (!first.sendTime || !second.sendTime) {
13835         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13836         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13837     }
13838     if (onmove->sendTime) {
13839       if (onmove->useColors) {
13840         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13841       }
13842       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13843     }
13844     if (onmove->useColors) {
13845       SendToProgram(onmove->twoMachinesColor, onmove);
13846     }
13847     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13848 //    SendToProgram("go\n", onmove);
13849     onmove->maybeThinking = TRUE;
13850     SetMachineThinkingEnables();
13851
13852     StartClocks();
13853
13854     if(bookHit) { // [HGM] book: simulate book reply
13855         static char bookMove[MSG_SIZ]; // a bit generous?
13856
13857         programStats.nodes = programStats.depth = programStats.time =
13858         programStats.score = programStats.got_only_move = 0;
13859         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13860
13861         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13862         strcat(bookMove, bookHit);
13863         savedMessage = bookMove; // args for deferred call
13864         savedState = onmove;
13865         ScheduleDelayedEvent(DeferredBookMove, 1);
13866     }
13867 }
13868
13869 void
13870 TrainingEvent ()
13871 {
13872     if (gameMode == Training) {
13873       SetTrainingModeOff();
13874       gameMode = PlayFromGameFile;
13875       DisplayMessage("", _("Training mode off"));
13876     } else {
13877       gameMode = Training;
13878       animateTraining = appData.animate;
13879
13880       /* make sure we are not already at the end of the game */
13881       if (currentMove < forwardMostMove) {
13882         SetTrainingModeOn();
13883         DisplayMessage("", _("Training mode on"));
13884       } else {
13885         gameMode = PlayFromGameFile;
13886         DisplayError(_("Already at end of game"), 0);
13887       }
13888     }
13889     ModeHighlight();
13890 }
13891
13892 void
13893 IcsClientEvent ()
13894 {
13895     if (!appData.icsActive) return;
13896     switch (gameMode) {
13897       case IcsPlayingWhite:
13898       case IcsPlayingBlack:
13899       case IcsObserving:
13900       case IcsIdle:
13901       case BeginningOfGame:
13902       case IcsExamining:
13903         return;
13904
13905       case EditGame:
13906         break;
13907
13908       case EditPosition:
13909         EditPositionDone(TRUE);
13910         break;
13911
13912       case AnalyzeMode:
13913       case AnalyzeFile:
13914         ExitAnalyzeMode();
13915         break;
13916
13917       default:
13918         EditGameEvent();
13919         break;
13920     }
13921
13922     gameMode = IcsIdle;
13923     ModeHighlight();
13924     return;
13925 }
13926
13927 void
13928 EditGameEvent ()
13929 {
13930     int i;
13931
13932     switch (gameMode) {
13933       case Training:
13934         SetTrainingModeOff();
13935         break;
13936       case MachinePlaysWhite:
13937       case MachinePlaysBlack:
13938       case BeginningOfGame:
13939         SendToProgram("force\n", &first);
13940         SetUserThinkingEnables();
13941         break;
13942       case PlayFromGameFile:
13943         (void) StopLoadGameTimer();
13944         if (gameFileFP != NULL) {
13945             gameFileFP = NULL;
13946         }
13947         break;
13948       case EditPosition:
13949         EditPositionDone(TRUE);
13950         break;
13951       case AnalyzeMode:
13952       case AnalyzeFile:
13953         ExitAnalyzeMode();
13954         SendToProgram("force\n", &first);
13955         break;
13956       case TwoMachinesPlay:
13957         GameEnds(EndOfFile, NULL, GE_PLAYER);
13958         ResurrectChessProgram();
13959         SetUserThinkingEnables();
13960         break;
13961       case EndOfGame:
13962         ResurrectChessProgram();
13963         break;
13964       case IcsPlayingBlack:
13965       case IcsPlayingWhite:
13966         DisplayError(_("Warning: You are still playing a game"), 0);
13967         break;
13968       case IcsObserving:
13969         DisplayError(_("Warning: You are still observing a game"), 0);
13970         break;
13971       case IcsExamining:
13972         DisplayError(_("Warning: You are still examining a game"), 0);
13973         break;
13974       case IcsIdle:
13975         break;
13976       case EditGame:
13977       default:
13978         return;
13979     }
13980
13981     pausing = FALSE;
13982     StopClocks();
13983     first.offeredDraw = second.offeredDraw = 0;
13984
13985     if (gameMode == PlayFromGameFile) {
13986         whiteTimeRemaining = timeRemaining[0][currentMove];
13987         blackTimeRemaining = timeRemaining[1][currentMove];
13988         DisplayTitle("");
13989     }
13990
13991     if (gameMode == MachinePlaysWhite ||
13992         gameMode == MachinePlaysBlack ||
13993         gameMode == TwoMachinesPlay ||
13994         gameMode == EndOfGame) {
13995         i = forwardMostMove;
13996         while (i > currentMove) {
13997             SendToProgram("undo\n", &first);
13998             i--;
13999         }
14000         if(!adjustedClock) {
14001         whiteTimeRemaining = timeRemaining[0][currentMove];
14002         blackTimeRemaining = timeRemaining[1][currentMove];
14003         DisplayBothClocks();
14004         }
14005         if (whiteFlag || blackFlag) {
14006             whiteFlag = blackFlag = 0;
14007         }
14008         DisplayTitle("");
14009     }
14010
14011     gameMode = EditGame;
14012     ModeHighlight();
14013     SetGameInfo();
14014 }
14015
14016
14017 void
14018 EditPositionEvent ()
14019 {
14020     if (gameMode == EditPosition) {
14021         EditGameEvent();
14022         return;
14023     }
14024
14025     EditGameEvent();
14026     if (gameMode != EditGame) return;
14027
14028     gameMode = EditPosition;
14029     ModeHighlight();
14030     SetGameInfo();
14031     if (currentMove > 0)
14032       CopyBoard(boards[0], boards[currentMove]);
14033
14034     blackPlaysFirst = !WhiteOnMove(currentMove);
14035     ResetClocks();
14036     currentMove = forwardMostMove = backwardMostMove = 0;
14037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14038     DisplayMove(-1);
14039     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14040 }
14041
14042 void
14043 ExitAnalyzeMode ()
14044 {
14045     /* [DM] icsEngineAnalyze - possible call from other functions */
14046     if (appData.icsEngineAnalyze) {
14047         appData.icsEngineAnalyze = FALSE;
14048
14049         DisplayMessage("",_("Close ICS engine analyze..."));
14050     }
14051     if (first.analysisSupport && first.analyzing) {
14052       SendToBoth("exit\n");
14053       first.analyzing = second.analyzing = FALSE;
14054     }
14055     thinkOutput[0] = NULLCHAR;
14056 }
14057
14058 void
14059 EditPositionDone (Boolean fakeRights)
14060 {
14061     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14062
14063     startedFromSetupPosition = TRUE;
14064     InitChessProgram(&first, FALSE);
14065     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14066       boards[0][EP_STATUS] = EP_NONE;
14067       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14068       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14069         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14070         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14071       } else boards[0][CASTLING][2] = NoRights;
14072       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14073         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14074         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14075       } else boards[0][CASTLING][5] = NoRights;
14076       if(gameInfo.variant == VariantSChess) {
14077         int i;
14078         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14079           boards[0][VIRGIN][i] = 0;
14080           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14081           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14082         }
14083       }
14084     }
14085     SendToProgram("force\n", &first);
14086     if (blackPlaysFirst) {
14087         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14088         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14089         currentMove = forwardMostMove = backwardMostMove = 1;
14090         CopyBoard(boards[1], boards[0]);
14091     } else {
14092         currentMove = forwardMostMove = backwardMostMove = 0;
14093     }
14094     SendBoard(&first, forwardMostMove);
14095     if (appData.debugMode) {
14096         fprintf(debugFP, "EditPosDone\n");
14097     }
14098     DisplayTitle("");
14099     DisplayMessage("", "");
14100     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14101     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14102     gameMode = EditGame;
14103     ModeHighlight();
14104     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14105     ClearHighlights(); /* [AS] */
14106 }
14107
14108 /* Pause for `ms' milliseconds */
14109 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14110 void
14111 TimeDelay (long ms)
14112 {
14113     TimeMark m1, m2;
14114
14115     GetTimeMark(&m1);
14116     do {
14117         GetTimeMark(&m2);
14118     } while (SubtractTimeMarks(&m2, &m1) < ms);
14119 }
14120
14121 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14122 void
14123 SendMultiLineToICS (char *buf)
14124 {
14125     char temp[MSG_SIZ+1], *p;
14126     int len;
14127
14128     len = strlen(buf);
14129     if (len > MSG_SIZ)
14130       len = MSG_SIZ;
14131
14132     strncpy(temp, buf, len);
14133     temp[len] = 0;
14134
14135     p = temp;
14136     while (*p) {
14137         if (*p == '\n' || *p == '\r')
14138           *p = ' ';
14139         ++p;
14140     }
14141
14142     strcat(temp, "\n");
14143     SendToICS(temp);
14144     SendToPlayer(temp, strlen(temp));
14145 }
14146
14147 void
14148 SetWhiteToPlayEvent ()
14149 {
14150     if (gameMode == EditPosition) {
14151         blackPlaysFirst = FALSE;
14152         DisplayBothClocks();    /* works because currentMove is 0 */
14153     } else if (gameMode == IcsExamining) {
14154         SendToICS(ics_prefix);
14155         SendToICS("tomove white\n");
14156     }
14157 }
14158
14159 void
14160 SetBlackToPlayEvent ()
14161 {
14162     if (gameMode == EditPosition) {
14163         blackPlaysFirst = TRUE;
14164         currentMove = 1;        /* kludge */
14165         DisplayBothClocks();
14166         currentMove = 0;
14167     } else if (gameMode == IcsExamining) {
14168         SendToICS(ics_prefix);
14169         SendToICS("tomove black\n");
14170     }
14171 }
14172
14173 void
14174 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14175 {
14176     char buf[MSG_SIZ];
14177     ChessSquare piece = boards[0][y][x];
14178
14179     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14180
14181     switch (selection) {
14182       case ClearBoard:
14183         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14184             SendToICS(ics_prefix);
14185             SendToICS("bsetup clear\n");
14186         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14187             SendToICS(ics_prefix);
14188             SendToICS("clearboard\n");
14189         } else {
14190             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14191                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14192                 for (y = 0; y < BOARD_HEIGHT; y++) {
14193                     if (gameMode == IcsExamining) {
14194                         if (boards[currentMove][y][x] != EmptySquare) {
14195                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14196                                     AAA + x, ONE + y);
14197                             SendToICS(buf);
14198                         }
14199                     } else {
14200                         boards[0][y][x] = p;
14201                     }
14202                 }
14203             }
14204         }
14205         if (gameMode == EditPosition) {
14206             DrawPosition(FALSE, boards[0]);
14207         }
14208         break;
14209
14210       case WhitePlay:
14211         SetWhiteToPlayEvent();
14212         break;
14213
14214       case BlackPlay:
14215         SetBlackToPlayEvent();
14216         break;
14217
14218       case EmptySquare:
14219         if (gameMode == IcsExamining) {
14220             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14221             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14222             SendToICS(buf);
14223         } else {
14224             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14225                 if(x == BOARD_LEFT-2) {
14226                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14227                     boards[0][y][1] = 0;
14228                 } else
14229                 if(x == BOARD_RGHT+1) {
14230                     if(y >= gameInfo.holdingsSize) break;
14231                     boards[0][y][BOARD_WIDTH-2] = 0;
14232                 } else break;
14233             }
14234             boards[0][y][x] = EmptySquare;
14235             DrawPosition(FALSE, boards[0]);
14236         }
14237         break;
14238
14239       case PromotePiece:
14240         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14241            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14242             selection = (ChessSquare) (PROMOTED piece);
14243         } else if(piece == EmptySquare) selection = WhiteSilver;
14244         else selection = (ChessSquare)((int)piece - 1);
14245         goto defaultlabel;
14246
14247       case DemotePiece:
14248         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14249            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14250             selection = (ChessSquare) (DEMOTED piece);
14251         } else if(piece == EmptySquare) selection = BlackSilver;
14252         else selection = (ChessSquare)((int)piece + 1);
14253         goto defaultlabel;
14254
14255       case WhiteQueen:
14256       case BlackQueen:
14257         if(gameInfo.variant == VariantShatranj ||
14258            gameInfo.variant == VariantXiangqi  ||
14259            gameInfo.variant == VariantCourier  ||
14260            gameInfo.variant == VariantMakruk     )
14261             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14262         goto defaultlabel;
14263
14264       case WhiteKing:
14265       case BlackKing:
14266         if(gameInfo.variant == VariantXiangqi)
14267             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14268         if(gameInfo.variant == VariantKnightmate)
14269             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14270       default:
14271         defaultlabel:
14272         if (gameMode == IcsExamining) {
14273             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14274             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14275                      PieceToChar(selection), AAA + x, ONE + y);
14276             SendToICS(buf);
14277         } else {
14278             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14279                 int n;
14280                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14281                     n = PieceToNumber(selection - BlackPawn);
14282                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14283                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14284                     boards[0][BOARD_HEIGHT-1-n][1]++;
14285                 } else
14286                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14287                     n = PieceToNumber(selection);
14288                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14289                     boards[0][n][BOARD_WIDTH-1] = selection;
14290                     boards[0][n][BOARD_WIDTH-2]++;
14291                 }
14292             } else
14293             boards[0][y][x] = selection;
14294             DrawPosition(TRUE, boards[0]);
14295             ClearHighlights();
14296             fromX = fromY = -1;
14297         }
14298         break;
14299     }
14300 }
14301
14302
14303 void
14304 DropMenuEvent (ChessSquare selection, int x, int y)
14305 {
14306     ChessMove moveType;
14307
14308     switch (gameMode) {
14309       case IcsPlayingWhite:
14310       case MachinePlaysBlack:
14311         if (!WhiteOnMove(currentMove)) {
14312             DisplayMoveError(_("It is Black's turn"));
14313             return;
14314         }
14315         moveType = WhiteDrop;
14316         break;
14317       case IcsPlayingBlack:
14318       case MachinePlaysWhite:
14319         if (WhiteOnMove(currentMove)) {
14320             DisplayMoveError(_("It is White's turn"));
14321             return;
14322         }
14323         moveType = BlackDrop;
14324         break;
14325       case EditGame:
14326         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14327         break;
14328       default:
14329         return;
14330     }
14331
14332     if (moveType == BlackDrop && selection < BlackPawn) {
14333       selection = (ChessSquare) ((int) selection
14334                                  + (int) BlackPawn - (int) WhitePawn);
14335     }
14336     if (boards[currentMove][y][x] != EmptySquare) {
14337         DisplayMoveError(_("That square is occupied"));
14338         return;
14339     }
14340
14341     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14342 }
14343
14344 void
14345 AcceptEvent ()
14346 {
14347     /* Accept a pending offer of any kind from opponent */
14348
14349     if (appData.icsActive) {
14350         SendToICS(ics_prefix);
14351         SendToICS("accept\n");
14352     } else if (cmailMsgLoaded) {
14353         if (currentMove == cmailOldMove &&
14354             commentList[cmailOldMove] != NULL &&
14355             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14356                    "Black offers a draw" : "White offers a draw")) {
14357             TruncateGame();
14358             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14359             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14360         } else {
14361             DisplayError(_("There is no pending offer on this move"), 0);
14362             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14363         }
14364     } else {
14365         /* Not used for offers from chess program */
14366     }
14367 }
14368
14369 void
14370 DeclineEvent ()
14371 {
14372     /* Decline a pending offer of any kind from opponent */
14373
14374     if (appData.icsActive) {
14375         SendToICS(ics_prefix);
14376         SendToICS("decline\n");
14377     } else if (cmailMsgLoaded) {
14378         if (currentMove == cmailOldMove &&
14379             commentList[cmailOldMove] != NULL &&
14380             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14381                    "Black offers a draw" : "White offers a draw")) {
14382 #ifdef NOTDEF
14383             AppendComment(cmailOldMove, "Draw declined", TRUE);
14384             DisplayComment(cmailOldMove - 1, "Draw declined");
14385 #endif /*NOTDEF*/
14386         } else {
14387             DisplayError(_("There is no pending offer on this move"), 0);
14388         }
14389     } else {
14390         /* Not used for offers from chess program */
14391     }
14392 }
14393
14394 void
14395 RematchEvent ()
14396 {
14397     /* Issue ICS rematch command */
14398     if (appData.icsActive) {
14399         SendToICS(ics_prefix);
14400         SendToICS("rematch\n");
14401     }
14402 }
14403
14404 void
14405 CallFlagEvent ()
14406 {
14407     /* Call your opponent's flag (claim a win on time) */
14408     if (appData.icsActive) {
14409         SendToICS(ics_prefix);
14410         SendToICS("flag\n");
14411     } else {
14412         switch (gameMode) {
14413           default:
14414             return;
14415           case MachinePlaysWhite:
14416             if (whiteFlag) {
14417                 if (blackFlag)
14418                   GameEnds(GameIsDrawn, "Both players ran out of time",
14419                            GE_PLAYER);
14420                 else
14421                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14422             } else {
14423                 DisplayError(_("Your opponent is not out of time"), 0);
14424             }
14425             break;
14426           case MachinePlaysBlack:
14427             if (blackFlag) {
14428                 if (whiteFlag)
14429                   GameEnds(GameIsDrawn, "Both players ran out of time",
14430                            GE_PLAYER);
14431                 else
14432                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14433             } else {
14434                 DisplayError(_("Your opponent is not out of time"), 0);
14435             }
14436             break;
14437         }
14438     }
14439 }
14440
14441 void
14442 ClockClick (int which)
14443 {       // [HGM] code moved to back-end from winboard.c
14444         if(which) { // black clock
14445           if (gameMode == EditPosition || gameMode == IcsExamining) {
14446             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14447             SetBlackToPlayEvent();
14448           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14449           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14450           } else if (shiftKey) {
14451             AdjustClock(which, -1);
14452           } else if (gameMode == IcsPlayingWhite ||
14453                      gameMode == MachinePlaysBlack) {
14454             CallFlagEvent();
14455           }
14456         } else { // white clock
14457           if (gameMode == EditPosition || gameMode == IcsExamining) {
14458             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14459             SetWhiteToPlayEvent();
14460           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14461           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14462           } else if (shiftKey) {
14463             AdjustClock(which, -1);
14464           } else if (gameMode == IcsPlayingBlack ||
14465                    gameMode == MachinePlaysWhite) {
14466             CallFlagEvent();
14467           }
14468         }
14469 }
14470
14471 void
14472 DrawEvent ()
14473 {
14474     /* Offer draw or accept pending draw offer from opponent */
14475
14476     if (appData.icsActive) {
14477         /* Note: tournament rules require draw offers to be
14478            made after you make your move but before you punch
14479            your clock.  Currently ICS doesn't let you do that;
14480            instead, you immediately punch your clock after making
14481            a move, but you can offer a draw at any time. */
14482
14483         SendToICS(ics_prefix);
14484         SendToICS("draw\n");
14485         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14486     } else if (cmailMsgLoaded) {
14487         if (currentMove == cmailOldMove &&
14488             commentList[cmailOldMove] != NULL &&
14489             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14490                    "Black offers a draw" : "White offers a draw")) {
14491             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14492             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14493         } else if (currentMove == cmailOldMove + 1) {
14494             char *offer = WhiteOnMove(cmailOldMove) ?
14495               "White offers a draw" : "Black offers a draw";
14496             AppendComment(currentMove, offer, TRUE);
14497             DisplayComment(currentMove - 1, offer);
14498             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14499         } else {
14500             DisplayError(_("You must make your move before offering a draw"), 0);
14501             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14502         }
14503     } else if (first.offeredDraw) {
14504         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14505     } else {
14506         if (first.sendDrawOffers) {
14507             SendToProgram("draw\n", &first);
14508             userOfferedDraw = TRUE;
14509         }
14510     }
14511 }
14512
14513 void
14514 AdjournEvent ()
14515 {
14516     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14517
14518     if (appData.icsActive) {
14519         SendToICS(ics_prefix);
14520         SendToICS("adjourn\n");
14521     } else {
14522         /* Currently GNU Chess doesn't offer or accept Adjourns */
14523     }
14524 }
14525
14526
14527 void
14528 AbortEvent ()
14529 {
14530     /* Offer Abort or accept pending Abort offer from opponent */
14531
14532     if (appData.icsActive) {
14533         SendToICS(ics_prefix);
14534         SendToICS("abort\n");
14535     } else {
14536         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14537     }
14538 }
14539
14540 void
14541 ResignEvent ()
14542 {
14543     /* Resign.  You can do this even if it's not your turn. */
14544
14545     if (appData.icsActive) {
14546         SendToICS(ics_prefix);
14547         SendToICS("resign\n");
14548     } else {
14549         switch (gameMode) {
14550           case MachinePlaysWhite:
14551             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14552             break;
14553           case MachinePlaysBlack:
14554             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14555             break;
14556           case EditGame:
14557             if (cmailMsgLoaded) {
14558                 TruncateGame();
14559                 if (WhiteOnMove(cmailOldMove)) {
14560                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14561                 } else {
14562                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14563                 }
14564                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14565             }
14566             break;
14567           default:
14568             break;
14569         }
14570     }
14571 }
14572
14573
14574 void
14575 StopObservingEvent ()
14576 {
14577     /* Stop observing current games */
14578     SendToICS(ics_prefix);
14579     SendToICS("unobserve\n");
14580 }
14581
14582 void
14583 StopExaminingEvent ()
14584 {
14585     /* Stop observing current game */
14586     SendToICS(ics_prefix);
14587     SendToICS("unexamine\n");
14588 }
14589
14590 void
14591 ForwardInner (int target)
14592 {
14593     int limit; int oldSeekGraphUp = seekGraphUp;
14594
14595     if (appData.debugMode)
14596         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14597                 target, currentMove, forwardMostMove);
14598
14599     if (gameMode == EditPosition)
14600       return;
14601
14602     seekGraphUp = FALSE;
14603     MarkTargetSquares(1);
14604
14605     if (gameMode == PlayFromGameFile && !pausing)
14606       PauseEvent();
14607
14608     if (gameMode == IcsExamining && pausing)
14609       limit = pauseExamForwardMostMove;
14610     else
14611       limit = forwardMostMove;
14612
14613     if (target > limit) target = limit;
14614
14615     if (target > 0 && moveList[target - 1][0]) {
14616         int fromX, fromY, toX, toY;
14617         toX = moveList[target - 1][2] - AAA;
14618         toY = moveList[target - 1][3] - ONE;
14619         if (moveList[target - 1][1] == '@') {
14620             if (appData.highlightLastMove) {
14621                 SetHighlights(-1, -1, toX, toY);
14622             }
14623         } else {
14624             fromX = moveList[target - 1][0] - AAA;
14625             fromY = moveList[target - 1][1] - ONE;
14626             if (target == currentMove + 1) {
14627                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14628             }
14629             if (appData.highlightLastMove) {
14630                 SetHighlights(fromX, fromY, toX, toY);
14631             }
14632         }
14633     }
14634     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14635         gameMode == Training || gameMode == PlayFromGameFile ||
14636         gameMode == AnalyzeFile) {
14637         while (currentMove < target) {
14638             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14639             SendMoveToProgram(currentMove++, &first);
14640         }
14641     } else {
14642         currentMove = target;
14643     }
14644
14645     if (gameMode == EditGame || gameMode == EndOfGame) {
14646         whiteTimeRemaining = timeRemaining[0][currentMove];
14647         blackTimeRemaining = timeRemaining[1][currentMove];
14648     }
14649     DisplayBothClocks();
14650     DisplayMove(currentMove - 1);
14651     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14652     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14653     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14654         DisplayComment(currentMove - 1, commentList[currentMove]);
14655     }
14656     ClearMap(); // [HGM] exclude: invalidate map
14657 }
14658
14659
14660 void
14661 ForwardEvent ()
14662 {
14663     if (gameMode == IcsExamining && !pausing) {
14664         SendToICS(ics_prefix);
14665         SendToICS("forward\n");
14666     } else {
14667         ForwardInner(currentMove + 1);
14668     }
14669 }
14670
14671 void
14672 ToEndEvent ()
14673 {
14674     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14675         /* to optimze, we temporarily turn off analysis mode while we feed
14676          * the remaining moves to the engine. Otherwise we get analysis output
14677          * after each move.
14678          */
14679         if (first.analysisSupport) {
14680           SendToProgram("exit\nforce\n", &first);
14681           first.analyzing = FALSE;
14682         }
14683     }
14684
14685     if (gameMode == IcsExamining && !pausing) {
14686         SendToICS(ics_prefix);
14687         SendToICS("forward 999999\n");
14688     } else {
14689         ForwardInner(forwardMostMove);
14690     }
14691
14692     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14693         /* we have fed all the moves, so reactivate analysis mode */
14694         SendToProgram("analyze\n", &first);
14695         first.analyzing = TRUE;
14696         /*first.maybeThinking = TRUE;*/
14697         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14698     }
14699 }
14700
14701 void
14702 BackwardInner (int target)
14703 {
14704     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14705
14706     if (appData.debugMode)
14707         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14708                 target, currentMove, forwardMostMove);
14709
14710     if (gameMode == EditPosition) return;
14711     seekGraphUp = FALSE;
14712     MarkTargetSquares(1);
14713     if (currentMove <= backwardMostMove) {
14714         ClearHighlights();
14715         DrawPosition(full_redraw, boards[currentMove]);
14716         return;
14717     }
14718     if (gameMode == PlayFromGameFile && !pausing)
14719       PauseEvent();
14720
14721     if (moveList[target][0]) {
14722         int fromX, fromY, toX, toY;
14723         toX = moveList[target][2] - AAA;
14724         toY = moveList[target][3] - ONE;
14725         if (moveList[target][1] == '@') {
14726             if (appData.highlightLastMove) {
14727                 SetHighlights(-1, -1, toX, toY);
14728             }
14729         } else {
14730             fromX = moveList[target][0] - AAA;
14731             fromY = moveList[target][1] - ONE;
14732             if (target == currentMove - 1) {
14733                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14734             }
14735             if (appData.highlightLastMove) {
14736                 SetHighlights(fromX, fromY, toX, toY);
14737             }
14738         }
14739     }
14740     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14741         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14742         while (currentMove > target) {
14743             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14744                 // null move cannot be undone. Reload program with move history before it.
14745                 int i;
14746                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14747                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14748                 }
14749                 SendBoard(&first, i); 
14750               if(second.analyzing) SendBoard(&second, i);
14751                 for(currentMove=i; currentMove<target; currentMove++) {
14752                     SendMoveToProgram(currentMove, &first);
14753                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14754                 }
14755                 break;
14756             }
14757             SendToBoth("undo\n");
14758             currentMove--;
14759         }
14760     } else {
14761         currentMove = target;
14762     }
14763
14764     if (gameMode == EditGame || gameMode == EndOfGame) {
14765         whiteTimeRemaining = timeRemaining[0][currentMove];
14766         blackTimeRemaining = timeRemaining[1][currentMove];
14767     }
14768     DisplayBothClocks();
14769     DisplayMove(currentMove - 1);
14770     DrawPosition(full_redraw, boards[currentMove]);
14771     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14772     // [HGM] PV info: routine tests if comment empty
14773     DisplayComment(currentMove - 1, commentList[currentMove]);
14774     ClearMap(); // [HGM] exclude: invalidate map
14775 }
14776
14777 void
14778 BackwardEvent ()
14779 {
14780     if (gameMode == IcsExamining && !pausing) {
14781         SendToICS(ics_prefix);
14782         SendToICS("backward\n");
14783     } else {
14784         BackwardInner(currentMove - 1);
14785     }
14786 }
14787
14788 void
14789 ToStartEvent ()
14790 {
14791     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14792         /* to optimize, we temporarily turn off analysis mode while we undo
14793          * all the moves. Otherwise we get analysis output after each undo.
14794          */
14795         if (first.analysisSupport) {
14796           SendToProgram("exit\nforce\n", &first);
14797           first.analyzing = FALSE;
14798         }
14799     }
14800
14801     if (gameMode == IcsExamining && !pausing) {
14802         SendToICS(ics_prefix);
14803         SendToICS("backward 999999\n");
14804     } else {
14805         BackwardInner(backwardMostMove);
14806     }
14807
14808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14809         /* we have fed all the moves, so reactivate analysis mode */
14810         SendToProgram("analyze\n", &first);
14811         first.analyzing = TRUE;
14812         /*first.maybeThinking = TRUE;*/
14813         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14814     }
14815 }
14816
14817 void
14818 ToNrEvent (int to)
14819 {
14820   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14821   if (to >= forwardMostMove) to = forwardMostMove;
14822   if (to <= backwardMostMove) to = backwardMostMove;
14823   if (to < currentMove) {
14824     BackwardInner(to);
14825   } else {
14826     ForwardInner(to);
14827   }
14828 }
14829
14830 void
14831 RevertEvent (Boolean annotate)
14832 {
14833     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14834         return;
14835     }
14836     if (gameMode != IcsExamining) {
14837         DisplayError(_("You are not examining a game"), 0);
14838         return;
14839     }
14840     if (pausing) {
14841         DisplayError(_("You can't revert while pausing"), 0);
14842         return;
14843     }
14844     SendToICS(ics_prefix);
14845     SendToICS("revert\n");
14846 }
14847
14848 void
14849 RetractMoveEvent ()
14850 {
14851     switch (gameMode) {
14852       case MachinePlaysWhite:
14853       case MachinePlaysBlack:
14854         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14855             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14856             return;
14857         }
14858         if (forwardMostMove < 2) return;
14859         currentMove = forwardMostMove = forwardMostMove - 2;
14860         whiteTimeRemaining = timeRemaining[0][currentMove];
14861         blackTimeRemaining = timeRemaining[1][currentMove];
14862         DisplayBothClocks();
14863         DisplayMove(currentMove - 1);
14864         ClearHighlights();/*!! could figure this out*/
14865         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14866         SendToProgram("remove\n", &first);
14867         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14868         break;
14869
14870       case BeginningOfGame:
14871       default:
14872         break;
14873
14874       case IcsPlayingWhite:
14875       case IcsPlayingBlack:
14876         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14877             SendToICS(ics_prefix);
14878             SendToICS("takeback 2\n");
14879         } else {
14880             SendToICS(ics_prefix);
14881             SendToICS("takeback 1\n");
14882         }
14883         break;
14884     }
14885 }
14886
14887 void
14888 MoveNowEvent ()
14889 {
14890     ChessProgramState *cps;
14891
14892     switch (gameMode) {
14893       case MachinePlaysWhite:
14894         if (!WhiteOnMove(forwardMostMove)) {
14895             DisplayError(_("It is your turn"), 0);
14896             return;
14897         }
14898         cps = &first;
14899         break;
14900       case MachinePlaysBlack:
14901         if (WhiteOnMove(forwardMostMove)) {
14902             DisplayError(_("It is your turn"), 0);
14903             return;
14904         }
14905         cps = &first;
14906         break;
14907       case TwoMachinesPlay:
14908         if (WhiteOnMove(forwardMostMove) ==
14909             (first.twoMachinesColor[0] == 'w')) {
14910             cps = &first;
14911         } else {
14912             cps = &second;
14913         }
14914         break;
14915       case BeginningOfGame:
14916       default:
14917         return;
14918     }
14919     SendToProgram("?\n", cps);
14920 }
14921
14922 void
14923 TruncateGameEvent ()
14924 {
14925     EditGameEvent();
14926     if (gameMode != EditGame) return;
14927     TruncateGame();
14928 }
14929
14930 void
14931 TruncateGame ()
14932 {
14933     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14934     if (forwardMostMove > currentMove) {
14935         if (gameInfo.resultDetails != NULL) {
14936             free(gameInfo.resultDetails);
14937             gameInfo.resultDetails = NULL;
14938             gameInfo.result = GameUnfinished;
14939         }
14940         forwardMostMove = currentMove;
14941         HistorySet(parseList, backwardMostMove, forwardMostMove,
14942                    currentMove-1);
14943     }
14944 }
14945
14946 void
14947 HintEvent ()
14948 {
14949     if (appData.noChessProgram) return;
14950     switch (gameMode) {
14951       case MachinePlaysWhite:
14952         if (WhiteOnMove(forwardMostMove)) {
14953             DisplayError(_("Wait until your turn"), 0);
14954             return;
14955         }
14956         break;
14957       case BeginningOfGame:
14958       case MachinePlaysBlack:
14959         if (!WhiteOnMove(forwardMostMove)) {
14960             DisplayError(_("Wait until your turn"), 0);
14961             return;
14962         }
14963         break;
14964       default:
14965         DisplayError(_("No hint available"), 0);
14966         return;
14967     }
14968     SendToProgram("hint\n", &first);
14969     hintRequested = TRUE;
14970 }
14971
14972 void
14973 BookEvent ()
14974 {
14975     if (appData.noChessProgram) return;
14976     switch (gameMode) {
14977       case MachinePlaysWhite:
14978         if (WhiteOnMove(forwardMostMove)) {
14979             DisplayError(_("Wait until your turn"), 0);
14980             return;
14981         }
14982         break;
14983       case BeginningOfGame:
14984       case MachinePlaysBlack:
14985         if (!WhiteOnMove(forwardMostMove)) {
14986             DisplayError(_("Wait until your turn"), 0);
14987             return;
14988         }
14989         break;
14990       case EditPosition:
14991         EditPositionDone(TRUE);
14992         break;
14993       case TwoMachinesPlay:
14994         return;
14995       default:
14996         break;
14997     }
14998     SendToProgram("bk\n", &first);
14999     bookOutput[0] = NULLCHAR;
15000     bookRequested = TRUE;
15001 }
15002
15003 void
15004 AboutGameEvent ()
15005 {
15006     char *tags = PGNTags(&gameInfo);
15007     TagsPopUp(tags, CmailMsg());
15008     free(tags);
15009 }
15010
15011 /* end button procedures */
15012
15013 void
15014 PrintPosition (FILE *fp, int move)
15015 {
15016     int i, j;
15017
15018     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15019         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15020             char c = PieceToChar(boards[move][i][j]);
15021             fputc(c == 'x' ? '.' : c, fp);
15022             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15023         }
15024     }
15025     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15026       fprintf(fp, "white to play\n");
15027     else
15028       fprintf(fp, "black to play\n");
15029 }
15030
15031 void
15032 PrintOpponents (FILE *fp)
15033 {
15034     if (gameInfo.white != NULL) {
15035         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15036     } else {
15037         fprintf(fp, "\n");
15038     }
15039 }
15040
15041 /* Find last component of program's own name, using some heuristics */
15042 void
15043 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15044 {
15045     char *p, *q, c;
15046     int local = (strcmp(host, "localhost") == 0);
15047     while (!local && (p = strchr(prog, ';')) != NULL) {
15048         p++;
15049         while (*p == ' ') p++;
15050         prog = p;
15051     }
15052     if (*prog == '"' || *prog == '\'') {
15053         q = strchr(prog + 1, *prog);
15054     } else {
15055         q = strchr(prog, ' ');
15056     }
15057     if (q == NULL) q = prog + strlen(prog);
15058     p = q;
15059     while (p >= prog && *p != '/' && *p != '\\') p--;
15060     p++;
15061     if(p == prog && *p == '"') p++;
15062     c = *q; *q = 0;
15063     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15064     memcpy(buf, p, q - p);
15065     buf[q - p] = NULLCHAR;
15066     if (!local) {
15067         strcat(buf, "@");
15068         strcat(buf, host);
15069     }
15070 }
15071
15072 char *
15073 TimeControlTagValue ()
15074 {
15075     char buf[MSG_SIZ];
15076     if (!appData.clockMode) {
15077       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15078     } else if (movesPerSession > 0) {
15079       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15080     } else if (timeIncrement == 0) {
15081       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15082     } else {
15083       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15084     }
15085     return StrSave(buf);
15086 }
15087
15088 void
15089 SetGameInfo ()
15090 {
15091     /* This routine is used only for certain modes */
15092     VariantClass v = gameInfo.variant;
15093     ChessMove r = GameUnfinished;
15094     char *p = NULL;
15095
15096     if(keepInfo) return;
15097
15098     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15099         r = gameInfo.result;
15100         p = gameInfo.resultDetails;
15101         gameInfo.resultDetails = NULL;
15102     }
15103     ClearGameInfo(&gameInfo);
15104     gameInfo.variant = v;
15105
15106     switch (gameMode) {
15107       case MachinePlaysWhite:
15108         gameInfo.event = StrSave( appData.pgnEventHeader );
15109         gameInfo.site = StrSave(HostName());
15110         gameInfo.date = PGNDate();
15111         gameInfo.round = StrSave("-");
15112         gameInfo.white = StrSave(first.tidy);
15113         gameInfo.black = StrSave(UserName());
15114         gameInfo.timeControl = TimeControlTagValue();
15115         break;
15116
15117       case MachinePlaysBlack:
15118         gameInfo.event = StrSave( appData.pgnEventHeader );
15119         gameInfo.site = StrSave(HostName());
15120         gameInfo.date = PGNDate();
15121         gameInfo.round = StrSave("-");
15122         gameInfo.white = StrSave(UserName());
15123         gameInfo.black = StrSave(first.tidy);
15124         gameInfo.timeControl = TimeControlTagValue();
15125         break;
15126
15127       case TwoMachinesPlay:
15128         gameInfo.event = StrSave( appData.pgnEventHeader );
15129         gameInfo.site = StrSave(HostName());
15130         gameInfo.date = PGNDate();
15131         if (roundNr > 0) {
15132             char buf[MSG_SIZ];
15133             snprintf(buf, MSG_SIZ, "%d", roundNr);
15134             gameInfo.round = StrSave(buf);
15135         } else {
15136             gameInfo.round = StrSave("-");
15137         }
15138         if (first.twoMachinesColor[0] == 'w') {
15139             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15140             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15141         } else {
15142             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15143             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15144         }
15145         gameInfo.timeControl = TimeControlTagValue();
15146         break;
15147
15148       case EditGame:
15149         gameInfo.event = StrSave("Edited game");
15150         gameInfo.site = StrSave(HostName());
15151         gameInfo.date = PGNDate();
15152         gameInfo.round = StrSave("-");
15153         gameInfo.white = StrSave("-");
15154         gameInfo.black = StrSave("-");
15155         gameInfo.result = r;
15156         gameInfo.resultDetails = p;
15157         break;
15158
15159       case EditPosition:
15160         gameInfo.event = StrSave("Edited position");
15161         gameInfo.site = StrSave(HostName());
15162         gameInfo.date = PGNDate();
15163         gameInfo.round = StrSave("-");
15164         gameInfo.white = StrSave("-");
15165         gameInfo.black = StrSave("-");
15166         break;
15167
15168       case IcsPlayingWhite:
15169       case IcsPlayingBlack:
15170       case IcsObserving:
15171       case IcsExamining:
15172         break;
15173
15174       case PlayFromGameFile:
15175         gameInfo.event = StrSave("Game from non-PGN file");
15176         gameInfo.site = StrSave(HostName());
15177         gameInfo.date = PGNDate();
15178         gameInfo.round = StrSave("-");
15179         gameInfo.white = StrSave("?");
15180         gameInfo.black = StrSave("?");
15181         break;
15182
15183       default:
15184         break;
15185     }
15186 }
15187
15188 void
15189 ReplaceComment (int index, char *text)
15190 {
15191     int len;
15192     char *p;
15193     float score;
15194
15195     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15196        pvInfoList[index-1].depth == len &&
15197        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15198        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15199     while (*text == '\n') text++;
15200     len = strlen(text);
15201     while (len > 0 && text[len - 1] == '\n') len--;
15202
15203     if (commentList[index] != NULL)
15204       free(commentList[index]);
15205
15206     if (len == 0) {
15207         commentList[index] = NULL;
15208         return;
15209     }
15210   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15211       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15212       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15213     commentList[index] = (char *) malloc(len + 2);
15214     strncpy(commentList[index], text, len);
15215     commentList[index][len] = '\n';
15216     commentList[index][len + 1] = NULLCHAR;
15217   } else {
15218     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15219     char *p;
15220     commentList[index] = (char *) malloc(len + 7);
15221     safeStrCpy(commentList[index], "{\n", 3);
15222     safeStrCpy(commentList[index]+2, text, len+1);
15223     commentList[index][len+2] = NULLCHAR;
15224     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15225     strcat(commentList[index], "\n}\n");
15226   }
15227 }
15228
15229 void
15230 CrushCRs (char *text)
15231 {
15232   char *p = text;
15233   char *q = text;
15234   char ch;
15235
15236   do {
15237     ch = *p++;
15238     if (ch == '\r') continue;
15239     *q++ = ch;
15240   } while (ch != '\0');
15241 }
15242
15243 void
15244 AppendComment (int index, char *text, Boolean addBraces)
15245 /* addBraces  tells if we should add {} */
15246 {
15247     int oldlen, len;
15248     char *old;
15249
15250 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15251     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15252
15253     CrushCRs(text);
15254     while (*text == '\n') text++;
15255     len = strlen(text);
15256     while (len > 0 && text[len - 1] == '\n') len--;
15257     text[len] = NULLCHAR;
15258
15259     if (len == 0) return;
15260
15261     if (commentList[index] != NULL) {
15262       Boolean addClosingBrace = addBraces;
15263         old = commentList[index];
15264         oldlen = strlen(old);
15265         while(commentList[index][oldlen-1] ==  '\n')
15266           commentList[index][--oldlen] = NULLCHAR;
15267         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15268         safeStrCpy(commentList[index], old, oldlen + len + 6);
15269         free(old);
15270         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15271         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15272           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15273           while (*text == '\n') { text++; len--; }
15274           commentList[index][--oldlen] = NULLCHAR;
15275       }
15276         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15277         else          strcat(commentList[index], "\n");
15278         strcat(commentList[index], text);
15279         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15280         else          strcat(commentList[index], "\n");
15281     } else {
15282         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15283         if(addBraces)
15284           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15285         else commentList[index][0] = NULLCHAR;
15286         strcat(commentList[index], text);
15287         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15288         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15289     }
15290 }
15291
15292 static char *
15293 FindStr (char * text, char * sub_text)
15294 {
15295     char * result = strstr( text, sub_text );
15296
15297     if( result != NULL ) {
15298         result += strlen( sub_text );
15299     }
15300
15301     return result;
15302 }
15303
15304 /* [AS] Try to extract PV info from PGN comment */
15305 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15306 char *
15307 GetInfoFromComment (int index, char * text)
15308 {
15309     char * sep = text, *p;
15310
15311     if( text != NULL && index > 0 ) {
15312         int score = 0;
15313         int depth = 0;
15314         int time = -1, sec = 0, deci;
15315         char * s_eval = FindStr( text, "[%eval " );
15316         char * s_emt = FindStr( text, "[%emt " );
15317
15318         if( s_eval != NULL || s_emt != NULL ) {
15319             /* New style */
15320             char delim;
15321
15322             if( s_eval != NULL ) {
15323                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15324                     return text;
15325                 }
15326
15327                 if( delim != ']' ) {
15328                     return text;
15329                 }
15330             }
15331
15332             if( s_emt != NULL ) {
15333             }
15334                 return text;
15335         }
15336         else {
15337             /* We expect something like: [+|-]nnn.nn/dd */
15338             int score_lo = 0;
15339
15340             if(*text != '{') return text; // [HGM] braces: must be normal comment
15341
15342             sep = strchr( text, '/' );
15343             if( sep == NULL || sep < (text+4) ) {
15344                 return text;
15345             }
15346
15347             p = text;
15348             if(p[1] == '(') { // comment starts with PV
15349                p = strchr(p, ')'); // locate end of PV
15350                if(p == NULL || sep < p+5) return text;
15351                // at this point we have something like "{(.*) +0.23/6 ..."
15352                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15353                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15354                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15355             }
15356             time = -1; sec = -1; deci = -1;
15357             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15358                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15359                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15360                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15361                 return text;
15362             }
15363
15364             if( score_lo < 0 || score_lo >= 100 ) {
15365                 return text;
15366             }
15367
15368             if(sec >= 0) time = 600*time + 10*sec; else
15369             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15370
15371             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15372
15373             /* [HGM] PV time: now locate end of PV info */
15374             while( *++sep >= '0' && *sep <= '9'); // strip depth
15375             if(time >= 0)
15376             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15377             if(sec >= 0)
15378             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15379             if(deci >= 0)
15380             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15381             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15382         }
15383
15384         if( depth <= 0 ) {
15385             return text;
15386         }
15387
15388         if( time < 0 ) {
15389             time = -1;
15390         }
15391
15392         pvInfoList[index-1].depth = depth;
15393         pvInfoList[index-1].score = score;
15394         pvInfoList[index-1].time  = 10*time; // centi-sec
15395         if(*sep == '}') *sep = 0; else *--sep = '{';
15396         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15397     }
15398     return sep;
15399 }
15400
15401 void
15402 SendToProgram (char *message, ChessProgramState *cps)
15403 {
15404     int count, outCount, error;
15405     char buf[MSG_SIZ];
15406
15407     if (cps->pr == NoProc) return;
15408     Attention(cps);
15409
15410     if (appData.debugMode) {
15411         TimeMark now;
15412         GetTimeMark(&now);
15413         fprintf(debugFP, "%ld >%-6s: %s",
15414                 SubtractTimeMarks(&now, &programStartTime),
15415                 cps->which, message);
15416         if(serverFP)
15417             fprintf(serverFP, "%ld >%-6s: %s",
15418                 SubtractTimeMarks(&now, &programStartTime),
15419                 cps->which, message), fflush(serverFP);
15420     }
15421
15422     count = strlen(message);
15423     outCount = OutputToProcess(cps->pr, message, count, &error);
15424     if (outCount < count && !exiting
15425                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15426       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15427       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15428         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15429             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15430                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15431                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15432                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15433             } else {
15434                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15435                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15436                 gameInfo.result = res;
15437             }
15438             gameInfo.resultDetails = StrSave(buf);
15439         }
15440         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15441         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15442     }
15443 }
15444
15445 void
15446 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15447 {
15448     char *end_str;
15449     char buf[MSG_SIZ];
15450     ChessProgramState *cps = (ChessProgramState *)closure;
15451
15452     if (isr != cps->isr) return; /* Killed intentionally */
15453     if (count <= 0) {
15454         if (count == 0) {
15455             RemoveInputSource(cps->isr);
15456             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15457                     _(cps->which), cps->program);
15458             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15459             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15460                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15461                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15462                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15463                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15464                 } else {
15465                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15466                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15467                     gameInfo.result = res;
15468                 }
15469                 gameInfo.resultDetails = StrSave(buf);
15470             }
15471             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15472             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15473         } else {
15474             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15475                     _(cps->which), cps->program);
15476             RemoveInputSource(cps->isr);
15477
15478             /* [AS] Program is misbehaving badly... kill it */
15479             if( count == -2 ) {
15480                 DestroyChildProcess( cps->pr, 9 );
15481                 cps->pr = NoProc;
15482             }
15483
15484             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15485         }
15486         return;
15487     }
15488
15489     if ((end_str = strchr(message, '\r')) != NULL)
15490       *end_str = NULLCHAR;
15491     if ((end_str = strchr(message, '\n')) != NULL)
15492       *end_str = NULLCHAR;
15493
15494     if (appData.debugMode) {
15495         TimeMark now; int print = 1;
15496         char *quote = ""; char c; int i;
15497
15498         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15499                 char start = message[0];
15500                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15501                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15502                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15503                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15504                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15505                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15506                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15507                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15508                    sscanf(message, "hint: %c", &c)!=1 && 
15509                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15510                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15511                     print = (appData.engineComments >= 2);
15512                 }
15513                 message[0] = start; // restore original message
15514         }
15515         if(print) {
15516                 GetTimeMark(&now);
15517                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15518                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15519                         quote,
15520                         message);
15521                 if(serverFP)
15522                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15523                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15524                         quote,
15525                         message), fflush(serverFP);
15526         }
15527     }
15528
15529     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15530     if (appData.icsEngineAnalyze) {
15531         if (strstr(message, "whisper") != NULL ||
15532              strstr(message, "kibitz") != NULL ||
15533             strstr(message, "tellics") != NULL) return;
15534     }
15535
15536     HandleMachineMove(message, cps);
15537 }
15538
15539
15540 void
15541 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15542 {
15543     char buf[MSG_SIZ];
15544     int seconds;
15545
15546     if( timeControl_2 > 0 ) {
15547         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15548             tc = timeControl_2;
15549         }
15550     }
15551     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15552     inc /= cps->timeOdds;
15553     st  /= cps->timeOdds;
15554
15555     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15556
15557     if (st > 0) {
15558       /* Set exact time per move, normally using st command */
15559       if (cps->stKludge) {
15560         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15561         seconds = st % 60;
15562         if (seconds == 0) {
15563           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15564         } else {
15565           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15566         }
15567       } else {
15568         snprintf(buf, MSG_SIZ, "st %d\n", st);
15569       }
15570     } else {
15571       /* Set conventional or incremental time control, using level command */
15572       if (seconds == 0) {
15573         /* Note old gnuchess bug -- minutes:seconds used to not work.
15574            Fixed in later versions, but still avoid :seconds
15575            when seconds is 0. */
15576         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15577       } else {
15578         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15579                  seconds, inc/1000.);
15580       }
15581     }
15582     SendToProgram(buf, cps);
15583
15584     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15585     /* Orthogonally, limit search to given depth */
15586     if (sd > 0) {
15587       if (cps->sdKludge) {
15588         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15589       } else {
15590         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15591       }
15592       SendToProgram(buf, cps);
15593     }
15594
15595     if(cps->nps >= 0) { /* [HGM] nps */
15596         if(cps->supportsNPS == FALSE)
15597           cps->nps = -1; // don't use if engine explicitly says not supported!
15598         else {
15599           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15600           SendToProgram(buf, cps);
15601         }
15602     }
15603 }
15604
15605 ChessProgramState *
15606 WhitePlayer ()
15607 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15608 {
15609     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15610        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15611         return &second;
15612     return &first;
15613 }
15614
15615 void
15616 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15617 {
15618     char message[MSG_SIZ];
15619     long time, otime;
15620
15621     /* Note: this routine must be called when the clocks are stopped
15622        or when they have *just* been set or switched; otherwise
15623        it will be off by the time since the current tick started.
15624     */
15625     if (machineWhite) {
15626         time = whiteTimeRemaining / 10;
15627         otime = blackTimeRemaining / 10;
15628     } else {
15629         time = blackTimeRemaining / 10;
15630         otime = whiteTimeRemaining / 10;
15631     }
15632     /* [HGM] translate opponent's time by time-odds factor */
15633     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15634
15635     if (time <= 0) time = 1;
15636     if (otime <= 0) otime = 1;
15637
15638     snprintf(message, MSG_SIZ, "time %ld\n", time);
15639     SendToProgram(message, cps);
15640
15641     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15642     SendToProgram(message, cps);
15643 }
15644
15645 int
15646 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15647 {
15648   char buf[MSG_SIZ];
15649   int len = strlen(name);
15650   int val;
15651
15652   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15653     (*p) += len + 1;
15654     sscanf(*p, "%d", &val);
15655     *loc = (val != 0);
15656     while (**p && **p != ' ')
15657       (*p)++;
15658     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15659     SendToProgram(buf, cps);
15660     return TRUE;
15661   }
15662   return FALSE;
15663 }
15664
15665 int
15666 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15667 {
15668   char buf[MSG_SIZ];
15669   int len = strlen(name);
15670   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15671     (*p) += len + 1;
15672     sscanf(*p, "%d", loc);
15673     while (**p && **p != ' ') (*p)++;
15674     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15675     SendToProgram(buf, cps);
15676     return TRUE;
15677   }
15678   return FALSE;
15679 }
15680
15681 int
15682 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15683 {
15684   char buf[MSG_SIZ];
15685   int len = strlen(name);
15686   if (strncmp((*p), name, len) == 0
15687       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15688     (*p) += len + 2;
15689     sscanf(*p, "%[^\"]", loc);
15690     while (**p && **p != '\"') (*p)++;
15691     if (**p == '\"') (*p)++;
15692     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15693     SendToProgram(buf, cps);
15694     return TRUE;
15695   }
15696   return FALSE;
15697 }
15698
15699 int
15700 ParseOption (Option *opt, ChessProgramState *cps)
15701 // [HGM] options: process the string that defines an engine option, and determine
15702 // name, type, default value, and allowed value range
15703 {
15704         char *p, *q, buf[MSG_SIZ];
15705         int n, min = (-1)<<31, max = 1<<31, def;
15706
15707         if(p = strstr(opt->name, " -spin ")) {
15708             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15709             if(max < min) max = min; // enforce consistency
15710             if(def < min) def = min;
15711             if(def > max) def = max;
15712             opt->value = def;
15713             opt->min = min;
15714             opt->max = max;
15715             opt->type = Spin;
15716         } else if((p = strstr(opt->name, " -slider "))) {
15717             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15718             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15719             if(max < min) max = min; // enforce consistency
15720             if(def < min) def = min;
15721             if(def > max) def = max;
15722             opt->value = def;
15723             opt->min = min;
15724             opt->max = max;
15725             opt->type = Spin; // Slider;
15726         } else if((p = strstr(opt->name, " -string "))) {
15727             opt->textValue = p+9;
15728             opt->type = TextBox;
15729         } else if((p = strstr(opt->name, " -file "))) {
15730             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15731             opt->textValue = p+7;
15732             opt->type = FileName; // FileName;
15733         } else if((p = strstr(opt->name, " -path "))) {
15734             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15735             opt->textValue = p+7;
15736             opt->type = PathName; // PathName;
15737         } else if(p = strstr(opt->name, " -check ")) {
15738             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15739             opt->value = (def != 0);
15740             opt->type = CheckBox;
15741         } else if(p = strstr(opt->name, " -combo ")) {
15742             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15743             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15744             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15745             opt->value = n = 0;
15746             while(q = StrStr(q, " /// ")) {
15747                 n++; *q = 0;    // count choices, and null-terminate each of them
15748                 q += 5;
15749                 if(*q == '*') { // remember default, which is marked with * prefix
15750                     q++;
15751                     opt->value = n;
15752                 }
15753                 cps->comboList[cps->comboCnt++] = q;
15754             }
15755             cps->comboList[cps->comboCnt++] = NULL;
15756             opt->max = n + 1;
15757             opt->type = ComboBox;
15758         } else if(p = strstr(opt->name, " -button")) {
15759             opt->type = Button;
15760         } else if(p = strstr(opt->name, " -save")) {
15761             opt->type = SaveButton;
15762         } else return FALSE;
15763         *p = 0; // terminate option name
15764         // now look if the command-line options define a setting for this engine option.
15765         if(cps->optionSettings && cps->optionSettings[0])
15766             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15767         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15768           snprintf(buf, MSG_SIZ, "option %s", p);
15769                 if(p = strstr(buf, ",")) *p = 0;
15770                 if(q = strchr(buf, '=')) switch(opt->type) {
15771                     case ComboBox:
15772                         for(n=0; n<opt->max; n++)
15773                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15774                         break;
15775                     case TextBox:
15776                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15777                         break;
15778                     case Spin:
15779                     case CheckBox:
15780                         opt->value = atoi(q+1);
15781                     default:
15782                         break;
15783                 }
15784                 strcat(buf, "\n");
15785                 SendToProgram(buf, cps);
15786         }
15787         return TRUE;
15788 }
15789
15790 void
15791 FeatureDone (ChessProgramState *cps, int val)
15792 {
15793   DelayedEventCallback cb = GetDelayedEvent();
15794   if ((cb == InitBackEnd3 && cps == &first) ||
15795       (cb == SettingsMenuIfReady && cps == &second) ||
15796       (cb == LoadEngine) ||
15797       (cb == TwoMachinesEventIfReady)) {
15798     CancelDelayedEvent();
15799     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15800   }
15801   cps->initDone = val;
15802 }
15803
15804 /* Parse feature command from engine */
15805 void
15806 ParseFeatures (char *args, ChessProgramState *cps)
15807 {
15808   char *p = args;
15809   char *q;
15810   int val;
15811   char buf[MSG_SIZ];
15812
15813   for (;;) {
15814     while (*p == ' ') p++;
15815     if (*p == NULLCHAR) return;
15816
15817     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15818     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15819     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15820     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15821     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15822     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15823     if (BoolFeature(&p, "reuse", &val, cps)) {
15824       /* Engine can disable reuse, but can't enable it if user said no */
15825       if (!val) cps->reuse = FALSE;
15826       continue;
15827     }
15828     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15829     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15830       if (gameMode == TwoMachinesPlay) {
15831         DisplayTwoMachinesTitle();
15832       } else {
15833         DisplayTitle("");
15834       }
15835       continue;
15836     }
15837     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15838     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15839     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15840     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15841     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15842     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15843     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15844     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15845     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15846     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15847     if (IntFeature(&p, "done", &val, cps)) {
15848       FeatureDone(cps, val);
15849       continue;
15850     }
15851     /* Added by Tord: */
15852     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15853     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15854     /* End of additions by Tord */
15855
15856     /* [HGM] added features: */
15857     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15858     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15859     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15860     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15861     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15862     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15863     if (StringFeature(&p, "option", buf, cps)) {
15864         FREE(cps->option[cps->nrOptions].name);
15865         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15866         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15867         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15868           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15869             SendToProgram(buf, cps);
15870             continue;
15871         }
15872         if(cps->nrOptions >= MAX_OPTIONS) {
15873             cps->nrOptions--;
15874             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15875             DisplayError(buf, 0);
15876         }
15877         continue;
15878     }
15879     /* End of additions by HGM */
15880
15881     /* unknown feature: complain and skip */
15882     q = p;
15883     while (*q && *q != '=') q++;
15884     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15885     SendToProgram(buf, cps);
15886     p = q;
15887     if (*p == '=') {
15888       p++;
15889       if (*p == '\"') {
15890         p++;
15891         while (*p && *p != '\"') p++;
15892         if (*p == '\"') p++;
15893       } else {
15894         while (*p && *p != ' ') p++;
15895       }
15896     }
15897   }
15898
15899 }
15900
15901 void
15902 PeriodicUpdatesEvent (int newState)
15903 {
15904     if (newState == appData.periodicUpdates)
15905       return;
15906
15907     appData.periodicUpdates=newState;
15908
15909     /* Display type changes, so update it now */
15910 //    DisplayAnalysis();
15911
15912     /* Get the ball rolling again... */
15913     if (newState) {
15914         AnalysisPeriodicEvent(1);
15915         StartAnalysisClock();
15916     }
15917 }
15918
15919 void
15920 PonderNextMoveEvent (int newState)
15921 {
15922     if (newState == appData.ponderNextMove) return;
15923     if (gameMode == EditPosition) EditPositionDone(TRUE);
15924     if (newState) {
15925         SendToProgram("hard\n", &first);
15926         if (gameMode == TwoMachinesPlay) {
15927             SendToProgram("hard\n", &second);
15928         }
15929     } else {
15930         SendToProgram("easy\n", &first);
15931         thinkOutput[0] = NULLCHAR;
15932         if (gameMode == TwoMachinesPlay) {
15933             SendToProgram("easy\n", &second);
15934         }
15935     }
15936     appData.ponderNextMove = newState;
15937 }
15938
15939 void
15940 NewSettingEvent (int option, int *feature, char *command, int value)
15941 {
15942     char buf[MSG_SIZ];
15943
15944     if (gameMode == EditPosition) EditPositionDone(TRUE);
15945     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15946     if(feature == NULL || *feature) SendToProgram(buf, &first);
15947     if (gameMode == TwoMachinesPlay) {
15948         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15949     }
15950 }
15951
15952 void
15953 ShowThinkingEvent ()
15954 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15955 {
15956     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15957     int newState = appData.showThinking
15958         // [HGM] thinking: other features now need thinking output as well
15959         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15960
15961     if (oldState == newState) return;
15962     oldState = newState;
15963     if (gameMode == EditPosition) EditPositionDone(TRUE);
15964     if (oldState) {
15965         SendToProgram("post\n", &first);
15966         if (gameMode == TwoMachinesPlay) {
15967             SendToProgram("post\n", &second);
15968         }
15969     } else {
15970         SendToProgram("nopost\n", &first);
15971         thinkOutput[0] = NULLCHAR;
15972         if (gameMode == TwoMachinesPlay) {
15973             SendToProgram("nopost\n", &second);
15974         }
15975     }
15976 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15977 }
15978
15979 void
15980 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15981 {
15982   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15983   if (pr == NoProc) return;
15984   AskQuestion(title, question, replyPrefix, pr);
15985 }
15986
15987 void
15988 TypeInEvent (char firstChar)
15989 {
15990     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15991         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15992         gameMode == AnalyzeMode || gameMode == EditGame || 
15993         gameMode == EditPosition || gameMode == IcsExamining ||
15994         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15995         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15996                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15997                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15998         gameMode == Training) PopUpMoveDialog(firstChar);
15999 }
16000
16001 void
16002 TypeInDoneEvent (char *move)
16003 {
16004         Board board;
16005         int n, fromX, fromY, toX, toY;
16006         char promoChar;
16007         ChessMove moveType;
16008
16009         // [HGM] FENedit
16010         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16011                 EditPositionPasteFEN(move);
16012                 return;
16013         }
16014         // [HGM] movenum: allow move number to be typed in any mode
16015         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16016           ToNrEvent(2*n-1);
16017           return;
16018         }
16019         // undocumented kludge: allow command-line option to be typed in!
16020         // (potentially fatal, and does not implement the effect of the option.)
16021         // should only be used for options that are values on which future decisions will be made,
16022         // and definitely not on options that would be used during initialization.
16023         if(strstr(move, "!!! -") == move) {
16024             ParseArgsFromString(move+4);
16025             return;
16026         }
16027
16028       if (gameMode != EditGame && currentMove != forwardMostMove && 
16029         gameMode != Training) {
16030         DisplayMoveError(_("Displayed move is not current"));
16031       } else {
16032         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16033           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16034         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16035         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16036           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16037           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16038         } else {
16039           DisplayMoveError(_("Could not parse move"));
16040         }
16041       }
16042 }
16043
16044 void
16045 DisplayMove (int moveNumber)
16046 {
16047     char message[MSG_SIZ];
16048     char res[MSG_SIZ];
16049     char cpThinkOutput[MSG_SIZ];
16050
16051     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16052
16053     if (moveNumber == forwardMostMove - 1 ||
16054         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16055
16056         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16057
16058         if (strchr(cpThinkOutput, '\n')) {
16059             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16060         }
16061     } else {
16062         *cpThinkOutput = NULLCHAR;
16063     }
16064
16065     /* [AS] Hide thinking from human user */
16066     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16067         *cpThinkOutput = NULLCHAR;
16068         if( thinkOutput[0] != NULLCHAR ) {
16069             int i;
16070
16071             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16072                 cpThinkOutput[i] = '.';
16073             }
16074             cpThinkOutput[i] = NULLCHAR;
16075             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16076         }
16077     }
16078
16079     if (moveNumber == forwardMostMove - 1 &&
16080         gameInfo.resultDetails != NULL) {
16081         if (gameInfo.resultDetails[0] == NULLCHAR) {
16082           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16083         } else {
16084           snprintf(res, MSG_SIZ, " {%s} %s",
16085                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16086         }
16087     } else {
16088         res[0] = NULLCHAR;
16089     }
16090
16091     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16092         DisplayMessage(res, cpThinkOutput);
16093     } else {
16094       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16095                 WhiteOnMove(moveNumber) ? " " : ".. ",
16096                 parseList[moveNumber], res);
16097         DisplayMessage(message, cpThinkOutput);
16098     }
16099 }
16100
16101 void
16102 DisplayComment (int moveNumber, char *text)
16103 {
16104     char title[MSG_SIZ];
16105
16106     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16107       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16108     } else {
16109       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16110               WhiteOnMove(moveNumber) ? " " : ".. ",
16111               parseList[moveNumber]);
16112     }
16113     if (text != NULL && (appData.autoDisplayComment || commentUp))
16114         CommentPopUp(title, text);
16115 }
16116
16117 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16118  * might be busy thinking or pondering.  It can be omitted if your
16119  * gnuchess is configured to stop thinking immediately on any user
16120  * input.  However, that gnuchess feature depends on the FIONREAD
16121  * ioctl, which does not work properly on some flavors of Unix.
16122  */
16123 void
16124 Attention (ChessProgramState *cps)
16125 {
16126 #if ATTENTION
16127     if (!cps->useSigint) return;
16128     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16129     switch (gameMode) {
16130       case MachinePlaysWhite:
16131       case MachinePlaysBlack:
16132       case TwoMachinesPlay:
16133       case IcsPlayingWhite:
16134       case IcsPlayingBlack:
16135       case AnalyzeMode:
16136       case AnalyzeFile:
16137         /* Skip if we know it isn't thinking */
16138         if (!cps->maybeThinking) return;
16139         if (appData.debugMode)
16140           fprintf(debugFP, "Interrupting %s\n", cps->which);
16141         InterruptChildProcess(cps->pr);
16142         cps->maybeThinking = FALSE;
16143         break;
16144       default:
16145         break;
16146     }
16147 #endif /*ATTENTION*/
16148 }
16149
16150 int
16151 CheckFlags ()
16152 {
16153     if (whiteTimeRemaining <= 0) {
16154         if (!whiteFlag) {
16155             whiteFlag = TRUE;
16156             if (appData.icsActive) {
16157                 if (appData.autoCallFlag &&
16158                     gameMode == IcsPlayingBlack && !blackFlag) {
16159                   SendToICS(ics_prefix);
16160                   SendToICS("flag\n");
16161                 }
16162             } else {
16163                 if (blackFlag) {
16164                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16165                 } else {
16166                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16167                     if (appData.autoCallFlag) {
16168                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16169                         return TRUE;
16170                     }
16171                 }
16172             }
16173         }
16174     }
16175     if (blackTimeRemaining <= 0) {
16176         if (!blackFlag) {
16177             blackFlag = TRUE;
16178             if (appData.icsActive) {
16179                 if (appData.autoCallFlag &&
16180                     gameMode == IcsPlayingWhite && !whiteFlag) {
16181                   SendToICS(ics_prefix);
16182                   SendToICS("flag\n");
16183                 }
16184             } else {
16185                 if (whiteFlag) {
16186                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16187                 } else {
16188                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16189                     if (appData.autoCallFlag) {
16190                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16191                         return TRUE;
16192                     }
16193                 }
16194             }
16195         }
16196     }
16197     return FALSE;
16198 }
16199
16200 void
16201 CheckTimeControl ()
16202 {
16203     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16204         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16205
16206     /*
16207      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16208      */
16209     if ( !WhiteOnMove(forwardMostMove) ) {
16210         /* White made time control */
16211         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16212         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16213         /* [HGM] time odds: correct new time quota for time odds! */
16214                                             / WhitePlayer()->timeOdds;
16215         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16216     } else {
16217         lastBlack -= blackTimeRemaining;
16218         /* Black made time control */
16219         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16220                                             / WhitePlayer()->other->timeOdds;
16221         lastWhite = whiteTimeRemaining;
16222     }
16223 }
16224
16225 void
16226 DisplayBothClocks ()
16227 {
16228     int wom = gameMode == EditPosition ?
16229       !blackPlaysFirst : WhiteOnMove(currentMove);
16230     DisplayWhiteClock(whiteTimeRemaining, wom);
16231     DisplayBlackClock(blackTimeRemaining, !wom);
16232 }
16233
16234
16235 /* Timekeeping seems to be a portability nightmare.  I think everyone
16236    has ftime(), but I'm really not sure, so I'm including some ifdefs
16237    to use other calls if you don't.  Clocks will be less accurate if
16238    you have neither ftime nor gettimeofday.
16239 */
16240
16241 /* VS 2008 requires the #include outside of the function */
16242 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16243 #include <sys/timeb.h>
16244 #endif
16245
16246 /* Get the current time as a TimeMark */
16247 void
16248 GetTimeMark (TimeMark *tm)
16249 {
16250 #if HAVE_GETTIMEOFDAY
16251
16252     struct timeval timeVal;
16253     struct timezone timeZone;
16254
16255     gettimeofday(&timeVal, &timeZone);
16256     tm->sec = (long) timeVal.tv_sec;
16257     tm->ms = (int) (timeVal.tv_usec / 1000L);
16258
16259 #else /*!HAVE_GETTIMEOFDAY*/
16260 #if HAVE_FTIME
16261
16262 // include <sys/timeb.h> / moved to just above start of function
16263     struct timeb timeB;
16264
16265     ftime(&timeB);
16266     tm->sec = (long) timeB.time;
16267     tm->ms = (int) timeB.millitm;
16268
16269 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16270     tm->sec = (long) time(NULL);
16271     tm->ms = 0;
16272 #endif
16273 #endif
16274 }
16275
16276 /* Return the difference in milliseconds between two
16277    time marks.  We assume the difference will fit in a long!
16278 */
16279 long
16280 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16281 {
16282     return 1000L*(tm2->sec - tm1->sec) +
16283            (long) (tm2->ms - tm1->ms);
16284 }
16285
16286
16287 /*
16288  * Code to manage the game clocks.
16289  *
16290  * In tournament play, black starts the clock and then white makes a move.
16291  * We give the human user a slight advantage if he is playing white---the
16292  * clocks don't run until he makes his first move, so it takes zero time.
16293  * Also, we don't account for network lag, so we could get out of sync
16294  * with GNU Chess's clock -- but then, referees are always right.
16295  */
16296
16297 static TimeMark tickStartTM;
16298 static long intendedTickLength;
16299
16300 long
16301 NextTickLength (long timeRemaining)
16302 {
16303     long nominalTickLength, nextTickLength;
16304
16305     if (timeRemaining > 0L && timeRemaining <= 10000L)
16306       nominalTickLength = 100L;
16307     else
16308       nominalTickLength = 1000L;
16309     nextTickLength = timeRemaining % nominalTickLength;
16310     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16311
16312     return nextTickLength;
16313 }
16314
16315 /* Adjust clock one minute up or down */
16316 void
16317 AdjustClock (Boolean which, int dir)
16318 {
16319     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16320     if(which) blackTimeRemaining += 60000*dir;
16321     else      whiteTimeRemaining += 60000*dir;
16322     DisplayBothClocks();
16323     adjustedClock = TRUE;
16324 }
16325
16326 /* Stop clocks and reset to a fresh time control */
16327 void
16328 ResetClocks ()
16329 {
16330     (void) StopClockTimer();
16331     if (appData.icsActive) {
16332         whiteTimeRemaining = blackTimeRemaining = 0;
16333     } else if (searchTime) {
16334         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16335         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16336     } else { /* [HGM] correct new time quote for time odds */
16337         whiteTC = blackTC = fullTimeControlString;
16338         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16339         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16340     }
16341     if (whiteFlag || blackFlag) {
16342         DisplayTitle("");
16343         whiteFlag = blackFlag = FALSE;
16344     }
16345     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16346     DisplayBothClocks();
16347     adjustedClock = FALSE;
16348 }
16349
16350 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16351
16352 /* Decrement running clock by amount of time that has passed */
16353 void
16354 DecrementClocks ()
16355 {
16356     long timeRemaining;
16357     long lastTickLength, fudge;
16358     TimeMark now;
16359
16360     if (!appData.clockMode) return;
16361     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16362
16363     GetTimeMark(&now);
16364
16365     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16366
16367     /* Fudge if we woke up a little too soon */
16368     fudge = intendedTickLength - lastTickLength;
16369     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16370
16371     if (WhiteOnMove(forwardMostMove)) {
16372         if(whiteNPS >= 0) lastTickLength = 0;
16373         timeRemaining = whiteTimeRemaining -= lastTickLength;
16374         if(timeRemaining < 0 && !appData.icsActive) {
16375             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16376             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16377                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16378                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16379             }
16380         }
16381         DisplayWhiteClock(whiteTimeRemaining - fudge,
16382                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16383     } else {
16384         if(blackNPS >= 0) lastTickLength = 0;
16385         timeRemaining = blackTimeRemaining -= lastTickLength;
16386         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16387             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16388             if(suddenDeath) {
16389                 blackStartMove = forwardMostMove;
16390                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16391             }
16392         }
16393         DisplayBlackClock(blackTimeRemaining - fudge,
16394                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16395     }
16396     if (CheckFlags()) return;
16397
16398     if(twoBoards) { // count down secondary board's clocks as well
16399         activePartnerTime -= lastTickLength;
16400         partnerUp = 1;
16401         if(activePartner == 'W')
16402             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16403         else
16404             DisplayBlackClock(activePartnerTime, TRUE);
16405         partnerUp = 0;
16406     }
16407
16408     tickStartTM = now;
16409     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16410     StartClockTimer(intendedTickLength);
16411
16412     /* if the time remaining has fallen below the alarm threshold, sound the
16413      * alarm. if the alarm has sounded and (due to a takeback or time control
16414      * with increment) the time remaining has increased to a level above the
16415      * threshold, reset the alarm so it can sound again.
16416      */
16417
16418     if (appData.icsActive && appData.icsAlarm) {
16419
16420         /* make sure we are dealing with the user's clock */
16421         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16422                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16423            )) return;
16424
16425         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16426             alarmSounded = FALSE;
16427         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16428             PlayAlarmSound();
16429             alarmSounded = TRUE;
16430         }
16431     }
16432 }
16433
16434
16435 /* A player has just moved, so stop the previously running
16436    clock and (if in clock mode) start the other one.
16437    We redisplay both clocks in case we're in ICS mode, because
16438    ICS gives us an update to both clocks after every move.
16439    Note that this routine is called *after* forwardMostMove
16440    is updated, so the last fractional tick must be subtracted
16441    from the color that is *not* on move now.
16442 */
16443 void
16444 SwitchClocks (int newMoveNr)
16445 {
16446     long lastTickLength;
16447     TimeMark now;
16448     int flagged = FALSE;
16449
16450     GetTimeMark(&now);
16451
16452     if (StopClockTimer() && appData.clockMode) {
16453         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16454         if (!WhiteOnMove(forwardMostMove)) {
16455             if(blackNPS >= 0) lastTickLength = 0;
16456             blackTimeRemaining -= lastTickLength;
16457            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16458 //         if(pvInfoList[forwardMostMove].time == -1)
16459                  pvInfoList[forwardMostMove].time =               // use GUI time
16460                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16461         } else {
16462            if(whiteNPS >= 0) lastTickLength = 0;
16463            whiteTimeRemaining -= lastTickLength;
16464            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16465 //         if(pvInfoList[forwardMostMove].time == -1)
16466                  pvInfoList[forwardMostMove].time =
16467                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16468         }
16469         flagged = CheckFlags();
16470     }
16471     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16472     CheckTimeControl();
16473
16474     if (flagged || !appData.clockMode) return;
16475
16476     switch (gameMode) {
16477       case MachinePlaysBlack:
16478       case MachinePlaysWhite:
16479       case BeginningOfGame:
16480         if (pausing) return;
16481         break;
16482
16483       case EditGame:
16484       case PlayFromGameFile:
16485       case IcsExamining:
16486         return;
16487
16488       default:
16489         break;
16490     }
16491
16492     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16493         if(WhiteOnMove(forwardMostMove))
16494              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16495         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16496     }
16497
16498     tickStartTM = now;
16499     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16500       whiteTimeRemaining : blackTimeRemaining);
16501     StartClockTimer(intendedTickLength);
16502 }
16503
16504
16505 /* Stop both clocks */
16506 void
16507 StopClocks ()
16508 {
16509     long lastTickLength;
16510     TimeMark now;
16511
16512     if (!StopClockTimer()) return;
16513     if (!appData.clockMode) return;
16514
16515     GetTimeMark(&now);
16516
16517     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16518     if (WhiteOnMove(forwardMostMove)) {
16519         if(whiteNPS >= 0) lastTickLength = 0;
16520         whiteTimeRemaining -= lastTickLength;
16521         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16522     } else {
16523         if(blackNPS >= 0) lastTickLength = 0;
16524         blackTimeRemaining -= lastTickLength;
16525         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16526     }
16527     CheckFlags();
16528 }
16529
16530 /* Start clock of player on move.  Time may have been reset, so
16531    if clock is already running, stop and restart it. */
16532 void
16533 StartClocks ()
16534 {
16535     (void) StopClockTimer(); /* in case it was running already */
16536     DisplayBothClocks();
16537     if (CheckFlags()) return;
16538
16539     if (!appData.clockMode) return;
16540     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16541
16542     GetTimeMark(&tickStartTM);
16543     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16544       whiteTimeRemaining : blackTimeRemaining);
16545
16546    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16547     whiteNPS = blackNPS = -1;
16548     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16549        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16550         whiteNPS = first.nps;
16551     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16552        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16553         blackNPS = first.nps;
16554     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16555         whiteNPS = second.nps;
16556     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16557         blackNPS = second.nps;
16558     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16559
16560     StartClockTimer(intendedTickLength);
16561 }
16562
16563 char *
16564 TimeString (long ms)
16565 {
16566     long second, minute, hour, day;
16567     char *sign = "";
16568     static char buf[32];
16569
16570     if (ms > 0 && ms <= 9900) {
16571       /* convert milliseconds to tenths, rounding up */
16572       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16573
16574       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16575       return buf;
16576     }
16577
16578     /* convert milliseconds to seconds, rounding up */
16579     /* use floating point to avoid strangeness of integer division
16580        with negative dividends on many machines */
16581     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16582
16583     if (second < 0) {
16584         sign = "-";
16585         second = -second;
16586     }
16587
16588     day = second / (60 * 60 * 24);
16589     second = second % (60 * 60 * 24);
16590     hour = second / (60 * 60);
16591     second = second % (60 * 60);
16592     minute = second / 60;
16593     second = second % 60;
16594
16595     if (day > 0)
16596       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16597               sign, day, hour, minute, second);
16598     else if (hour > 0)
16599       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16600     else
16601       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16602
16603     return buf;
16604 }
16605
16606
16607 /*
16608  * This is necessary because some C libraries aren't ANSI C compliant yet.
16609  */
16610 char *
16611 StrStr (char *string, char *match)
16612 {
16613     int i, length;
16614
16615     length = strlen(match);
16616
16617     for (i = strlen(string) - length; i >= 0; i--, string++)
16618       if (!strncmp(match, string, length))
16619         return string;
16620
16621     return NULL;
16622 }
16623
16624 char *
16625 StrCaseStr (char *string, char *match)
16626 {
16627     int i, j, length;
16628
16629     length = strlen(match);
16630
16631     for (i = strlen(string) - length; i >= 0; i--, string++) {
16632         for (j = 0; j < length; j++) {
16633             if (ToLower(match[j]) != ToLower(string[j]))
16634               break;
16635         }
16636         if (j == length) return string;
16637     }
16638
16639     return NULL;
16640 }
16641
16642 #ifndef _amigados
16643 int
16644 StrCaseCmp (char *s1, char *s2)
16645 {
16646     char c1, c2;
16647
16648     for (;;) {
16649         c1 = ToLower(*s1++);
16650         c2 = ToLower(*s2++);
16651         if (c1 > c2) return 1;
16652         if (c1 < c2) return -1;
16653         if (c1 == NULLCHAR) return 0;
16654     }
16655 }
16656
16657
16658 int
16659 ToLower (int c)
16660 {
16661     return isupper(c) ? tolower(c) : c;
16662 }
16663
16664
16665 int
16666 ToUpper (int c)
16667 {
16668     return islower(c) ? toupper(c) : c;
16669 }
16670 #endif /* !_amigados    */
16671
16672 char *
16673 StrSave (char *s)
16674 {
16675   char *ret;
16676
16677   if ((ret = (char *) malloc(strlen(s) + 1)))
16678     {
16679       safeStrCpy(ret, s, strlen(s)+1);
16680     }
16681   return ret;
16682 }
16683
16684 char *
16685 StrSavePtr (char *s, char **savePtr)
16686 {
16687     if (*savePtr) {
16688         free(*savePtr);
16689     }
16690     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16691       safeStrCpy(*savePtr, s, strlen(s)+1);
16692     }
16693     return(*savePtr);
16694 }
16695
16696 char *
16697 PGNDate ()
16698 {
16699     time_t clock;
16700     struct tm *tm;
16701     char buf[MSG_SIZ];
16702
16703     clock = time((time_t *)NULL);
16704     tm = localtime(&clock);
16705     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16706             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16707     return StrSave(buf);
16708 }
16709
16710
16711 char *
16712 PositionToFEN (int move, char *overrideCastling)
16713 {
16714     int i, j, fromX, fromY, toX, toY;
16715     int whiteToPlay;
16716     char buf[MSG_SIZ];
16717     char *p, *q;
16718     int emptycount;
16719     ChessSquare piece;
16720
16721     whiteToPlay = (gameMode == EditPosition) ?
16722       !blackPlaysFirst : (move % 2 == 0);
16723     p = buf;
16724
16725     /* Piece placement data */
16726     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16727         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16728         emptycount = 0;
16729         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16730             if (boards[move][i][j] == EmptySquare) {
16731                 emptycount++;
16732             } else { ChessSquare piece = boards[move][i][j];
16733                 if (emptycount > 0) {
16734                     if(emptycount<10) /* [HGM] can be >= 10 */
16735                         *p++ = '0' + emptycount;
16736                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16737                     emptycount = 0;
16738                 }
16739                 if(PieceToChar(piece) == '+') {
16740                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16741                     *p++ = '+';
16742                     piece = (ChessSquare)(DEMOTED piece);
16743                 }
16744                 *p++ = PieceToChar(piece);
16745                 if(p[-1] == '~') {
16746                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16747                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16748                     *p++ = '~';
16749                 }
16750             }
16751         }
16752         if (emptycount > 0) {
16753             if(emptycount<10) /* [HGM] can be >= 10 */
16754                 *p++ = '0' + emptycount;
16755             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16756             emptycount = 0;
16757         }
16758         *p++ = '/';
16759     }
16760     *(p - 1) = ' ';
16761
16762     /* [HGM] print Crazyhouse or Shogi holdings */
16763     if( gameInfo.holdingsWidth ) {
16764         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16765         q = p;
16766         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16767             piece = boards[move][i][BOARD_WIDTH-1];
16768             if( piece != EmptySquare )
16769               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16770                   *p++ = PieceToChar(piece);
16771         }
16772         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16773             piece = boards[move][BOARD_HEIGHT-i-1][0];
16774             if( piece != EmptySquare )
16775               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16776                   *p++ = PieceToChar(piece);
16777         }
16778
16779         if( q == p ) *p++ = '-';
16780         *p++ = ']';
16781         *p++ = ' ';
16782     }
16783
16784     /* Active color */
16785     *p++ = whiteToPlay ? 'w' : 'b';
16786     *p++ = ' ';
16787
16788   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16789     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16790   } else {
16791   if(nrCastlingRights) {
16792      q = p;
16793      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16794        /* [HGM] write directly from rights */
16795            if(boards[move][CASTLING][2] != NoRights &&
16796               boards[move][CASTLING][0] != NoRights   )
16797                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16798            if(boards[move][CASTLING][2] != NoRights &&
16799               boards[move][CASTLING][1] != NoRights   )
16800                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16801            if(boards[move][CASTLING][5] != NoRights &&
16802               boards[move][CASTLING][3] != NoRights   )
16803                 *p++ = boards[move][CASTLING][3] + AAA;
16804            if(boards[move][CASTLING][5] != NoRights &&
16805               boards[move][CASTLING][4] != NoRights   )
16806                 *p++ = boards[move][CASTLING][4] + AAA;
16807      } else {
16808
16809         /* [HGM] write true castling rights */
16810         if( nrCastlingRights == 6 ) {
16811             int q, k=0;
16812             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16813                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16814             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16815                  boards[move][CASTLING][2] != NoRights  );
16816             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16817                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16818                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16819                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16820                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16821             }
16822             if(q) *p++ = 'Q';
16823             k = 0;
16824             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16825                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16826             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16827                  boards[move][CASTLING][5] != NoRights  );
16828             if(gameInfo.variant == VariantSChess) {
16829                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16830                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16831                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16832                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16833             }
16834             if(q) *p++ = 'q';
16835         }
16836      }
16837      if (q == p) *p++ = '-'; /* No castling rights */
16838      *p++ = ' ';
16839   }
16840
16841   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16842      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16843     /* En passant target square */
16844     if (move > backwardMostMove) {
16845         fromX = moveList[move - 1][0] - AAA;
16846         fromY = moveList[move - 1][1] - ONE;
16847         toX = moveList[move - 1][2] - AAA;
16848         toY = moveList[move - 1][3] - ONE;
16849         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16850             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16851             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16852             fromX == toX) {
16853             /* 2-square pawn move just happened */
16854             *p++ = toX + AAA;
16855             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16856         } else {
16857             *p++ = '-';
16858         }
16859     } else if(move == backwardMostMove) {
16860         // [HGM] perhaps we should always do it like this, and forget the above?
16861         if((signed char)boards[move][EP_STATUS] >= 0) {
16862             *p++ = boards[move][EP_STATUS] + AAA;
16863             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16864         } else {
16865             *p++ = '-';
16866         }
16867     } else {
16868         *p++ = '-';
16869     }
16870     *p++ = ' ';
16871   }
16872   }
16873
16874     /* [HGM] find reversible plies */
16875     {   int i = 0, j=move;
16876
16877         if (appData.debugMode) { int k;
16878             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16879             for(k=backwardMostMove; k<=forwardMostMove; k++)
16880                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16881
16882         }
16883
16884         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16885         if( j == backwardMostMove ) i += initialRulePlies;
16886         sprintf(p, "%d ", i);
16887         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16888     }
16889     /* Fullmove number */
16890     sprintf(p, "%d", (move / 2) + 1);
16891
16892     return StrSave(buf);
16893 }
16894
16895 Boolean
16896 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16897 {
16898     int i, j;
16899     char *p, c;
16900     int emptycount, virgin[BOARD_FILES];
16901     ChessSquare piece;
16902
16903     p = fen;
16904
16905     /* [HGM] by default clear Crazyhouse holdings, if present */
16906     if(gameInfo.holdingsWidth) {
16907        for(i=0; i<BOARD_HEIGHT; i++) {
16908            board[i][0]             = EmptySquare; /* black holdings */
16909            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16910            board[i][1]             = (ChessSquare) 0; /* black counts */
16911            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16912        }
16913     }
16914
16915     /* Piece placement data */
16916     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16917         j = 0;
16918         for (;;) {
16919             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16920                 if (*p == '/') p++;
16921                 emptycount = gameInfo.boardWidth - j;
16922                 while (emptycount--)
16923                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16924                 break;
16925 #if(BOARD_FILES >= 10)
16926             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16927                 p++; emptycount=10;
16928                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16929                 while (emptycount--)
16930                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16931 #endif
16932             } else if (isdigit(*p)) {
16933                 emptycount = *p++ - '0';
16934                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16935                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16936                 while (emptycount--)
16937                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16938             } else if (*p == '+' || isalpha(*p)) {
16939                 if (j >= gameInfo.boardWidth) return FALSE;
16940                 if(*p=='+') {
16941                     piece = CharToPiece(*++p);
16942                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16943                     piece = (ChessSquare) (PROMOTED piece ); p++;
16944                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16945                 } else piece = CharToPiece(*p++);
16946
16947                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16948                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16949                     piece = (ChessSquare) (PROMOTED piece);
16950                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16951                     p++;
16952                 }
16953                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16954             } else {
16955                 return FALSE;
16956             }
16957         }
16958     }
16959     while (*p == '/' || *p == ' ') p++;
16960
16961     /* [HGM] look for Crazyhouse holdings here */
16962     while(*p==' ') p++;
16963     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16964         if(*p == '[') p++;
16965         if(*p == '-' ) p++; /* empty holdings */ else {
16966             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16967             /* if we would allow FEN reading to set board size, we would   */
16968             /* have to add holdings and shift the board read so far here   */
16969             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16970                 p++;
16971                 if((int) piece >= (int) BlackPawn ) {
16972                     i = (int)piece - (int)BlackPawn;
16973                     i = PieceToNumber((ChessSquare)i);
16974                     if( i >= gameInfo.holdingsSize ) return FALSE;
16975                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16976                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16977                 } else {
16978                     i = (int)piece - (int)WhitePawn;
16979                     i = PieceToNumber((ChessSquare)i);
16980                     if( i >= gameInfo.holdingsSize ) return FALSE;
16981                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16982                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16983                 }
16984             }
16985         }
16986         if(*p == ']') p++;
16987     }
16988
16989     while(*p == ' ') p++;
16990
16991     /* Active color */
16992     c = *p++;
16993     if(appData.colorNickNames) {
16994       if( c == appData.colorNickNames[0] ) c = 'w'; else
16995       if( c == appData.colorNickNames[1] ) c = 'b';
16996     }
16997     switch (c) {
16998       case 'w':
16999         *blackPlaysFirst = FALSE;
17000         break;
17001       case 'b':
17002         *blackPlaysFirst = TRUE;
17003         break;
17004       default:
17005         return FALSE;
17006     }
17007
17008     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17009     /* return the extra info in global variiables             */
17010
17011     /* set defaults in case FEN is incomplete */
17012     board[EP_STATUS] = EP_UNKNOWN;
17013     for(i=0; i<nrCastlingRights; i++ ) {
17014         board[CASTLING][i] =
17015             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17016     }   /* assume possible unless obviously impossible */
17017     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17018     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17019     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17020                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17021     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17022     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17023     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17024                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17025     FENrulePlies = 0;
17026
17027     while(*p==' ') p++;
17028     if(nrCastlingRights) {
17029       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17030       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17031           /* castling indicator present, so default becomes no castlings */
17032           for(i=0; i<nrCastlingRights; i++ ) {
17033                  board[CASTLING][i] = NoRights;
17034           }
17035       }
17036       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17037              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17038              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17039              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17040         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17041
17042         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17043             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17044             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17045         }
17046         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17047             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17048         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17049                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17050         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17051                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17052         switch(c) {
17053           case'K':
17054               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17055               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17056               board[CASTLING][2] = whiteKingFile;
17057               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17058               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17059               break;
17060           case'Q':
17061               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17062               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17063               board[CASTLING][2] = whiteKingFile;
17064               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17065               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17066               break;
17067           case'k':
17068               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17069               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17070               board[CASTLING][5] = blackKingFile;
17071               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17072               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17073               break;
17074           case'q':
17075               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17076               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17077               board[CASTLING][5] = blackKingFile;
17078               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17079               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17080           case '-':
17081               break;
17082           default: /* FRC castlings */
17083               if(c >= 'a') { /* black rights */
17084                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17085                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17086                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17087                   if(i == BOARD_RGHT) break;
17088                   board[CASTLING][5] = i;
17089                   c -= AAA;
17090                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17091                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17092                   if(c > i)
17093                       board[CASTLING][3] = c;
17094                   else
17095                       board[CASTLING][4] = c;
17096               } else { /* white rights */
17097                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17098                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17099                     if(board[0][i] == WhiteKing) break;
17100                   if(i == BOARD_RGHT) break;
17101                   board[CASTLING][2] = i;
17102                   c -= AAA - 'a' + 'A';
17103                   if(board[0][c] >= WhiteKing) break;
17104                   if(c > i)
17105                       board[CASTLING][0] = c;
17106                   else
17107                       board[CASTLING][1] = c;
17108               }
17109         }
17110       }
17111       for(i=0; i<nrCastlingRights; i++)
17112         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17113       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17114     if (appData.debugMode) {
17115         fprintf(debugFP, "FEN castling rights:");
17116         for(i=0; i<nrCastlingRights; i++)
17117         fprintf(debugFP, " %d", board[CASTLING][i]);
17118         fprintf(debugFP, "\n");
17119     }
17120
17121       while(*p==' ') p++;
17122     }
17123
17124     /* read e.p. field in games that know e.p. capture */
17125     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17126        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17127       if(*p=='-') {
17128         p++; board[EP_STATUS] = EP_NONE;
17129       } else {
17130          char c = *p++ - AAA;
17131
17132          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17133          if(*p >= '0' && *p <='9') p++;
17134          board[EP_STATUS] = c;
17135       }
17136     }
17137
17138
17139     if(sscanf(p, "%d", &i) == 1) {
17140         FENrulePlies = i; /* 50-move ply counter */
17141         /* (The move number is still ignored)    */
17142     }
17143
17144     return TRUE;
17145 }
17146
17147 void
17148 EditPositionPasteFEN (char *fen)
17149 {
17150   if (fen != NULL) {
17151     Board initial_position;
17152
17153     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17154       DisplayError(_("Bad FEN position in clipboard"), 0);
17155       return ;
17156     } else {
17157       int savedBlackPlaysFirst = blackPlaysFirst;
17158       EditPositionEvent();
17159       blackPlaysFirst = savedBlackPlaysFirst;
17160       CopyBoard(boards[0], initial_position);
17161       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17162       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17163       DisplayBothClocks();
17164       DrawPosition(FALSE, boards[currentMove]);
17165     }
17166   }
17167 }
17168
17169 static char cseq[12] = "\\   ";
17170
17171 Boolean
17172 set_cont_sequence (char *new_seq)
17173 {
17174     int len;
17175     Boolean ret;
17176
17177     // handle bad attempts to set the sequence
17178         if (!new_seq)
17179                 return 0; // acceptable error - no debug
17180
17181     len = strlen(new_seq);
17182     ret = (len > 0) && (len < sizeof(cseq));
17183     if (ret)
17184       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17185     else if (appData.debugMode)
17186       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17187     return ret;
17188 }
17189
17190 /*
17191     reformat a source message so words don't cross the width boundary.  internal
17192     newlines are not removed.  returns the wrapped size (no null character unless
17193     included in source message).  If dest is NULL, only calculate the size required
17194     for the dest buffer.  lp argument indicats line position upon entry, and it's
17195     passed back upon exit.
17196 */
17197 int
17198 wrap (char *dest, char *src, int count, int width, int *lp)
17199 {
17200     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17201
17202     cseq_len = strlen(cseq);
17203     old_line = line = *lp;
17204     ansi = len = clen = 0;
17205
17206     for (i=0; i < count; i++)
17207     {
17208         if (src[i] == '\033')
17209             ansi = 1;
17210
17211         // if we hit the width, back up
17212         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17213         {
17214             // store i & len in case the word is too long
17215             old_i = i, old_len = len;
17216
17217             // find the end of the last word
17218             while (i && src[i] != ' ' && src[i] != '\n')
17219             {
17220                 i--;
17221                 len--;
17222             }
17223
17224             // word too long?  restore i & len before splitting it
17225             if ((old_i-i+clen) >= width)
17226             {
17227                 i = old_i;
17228                 len = old_len;
17229             }
17230
17231             // extra space?
17232             if (i && src[i-1] == ' ')
17233                 len--;
17234
17235             if (src[i] != ' ' && src[i] != '\n')
17236             {
17237                 i--;
17238                 if (len)
17239                     len--;
17240             }
17241
17242             // now append the newline and continuation sequence
17243             if (dest)
17244                 dest[len] = '\n';
17245             len++;
17246             if (dest)
17247                 strncpy(dest+len, cseq, cseq_len);
17248             len += cseq_len;
17249             line = cseq_len;
17250             clen = cseq_len;
17251             continue;
17252         }
17253
17254         if (dest)
17255             dest[len] = src[i];
17256         len++;
17257         if (!ansi)
17258             line++;
17259         if (src[i] == '\n')
17260             line = 0;
17261         if (src[i] == 'm')
17262             ansi = 0;
17263     }
17264     if (dest && appData.debugMode)
17265     {
17266         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17267             count, width, line, len, *lp);
17268         show_bytes(debugFP, src, count);
17269         fprintf(debugFP, "\ndest: ");
17270         show_bytes(debugFP, dest, len);
17271         fprintf(debugFP, "\n");
17272     }
17273     *lp = dest ? line : old_line;
17274
17275     return len;
17276 }
17277
17278 // [HGM] vari: routines for shelving variations
17279 Boolean modeRestore = FALSE;
17280
17281 void
17282 PushInner (int firstMove, int lastMove)
17283 {
17284         int i, j, nrMoves = lastMove - firstMove;
17285
17286         // push current tail of game on stack
17287         savedResult[storedGames] = gameInfo.result;
17288         savedDetails[storedGames] = gameInfo.resultDetails;
17289         gameInfo.resultDetails = NULL;
17290         savedFirst[storedGames] = firstMove;
17291         savedLast [storedGames] = lastMove;
17292         savedFramePtr[storedGames] = framePtr;
17293         framePtr -= nrMoves; // reserve space for the boards
17294         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17295             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17296             for(j=0; j<MOVE_LEN; j++)
17297                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17298             for(j=0; j<2*MOVE_LEN; j++)
17299                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17300             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17301             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17302             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17303             pvInfoList[firstMove+i-1].depth = 0;
17304             commentList[framePtr+i] = commentList[firstMove+i];
17305             commentList[firstMove+i] = NULL;
17306         }
17307
17308         storedGames++;
17309         forwardMostMove = firstMove; // truncate game so we can start variation
17310 }
17311
17312 void
17313 PushTail (int firstMove, int lastMove)
17314 {
17315         if(appData.icsActive) { // only in local mode
17316                 forwardMostMove = currentMove; // mimic old ICS behavior
17317                 return;
17318         }
17319         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17320
17321         PushInner(firstMove, lastMove);
17322         if(storedGames == 1) GreyRevert(FALSE);
17323         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17324 }
17325
17326 void
17327 PopInner (Boolean annotate)
17328 {
17329         int i, j, nrMoves;
17330         char buf[8000], moveBuf[20];
17331
17332         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17333         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17334         nrMoves = savedLast[storedGames] - currentMove;
17335         if(annotate) {
17336                 int cnt = 10;
17337                 if(!WhiteOnMove(currentMove))
17338                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17339                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17340                 for(i=currentMove; i<forwardMostMove; i++) {
17341                         if(WhiteOnMove(i))
17342                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17343                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17344                         strcat(buf, moveBuf);
17345                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17346                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17347                 }
17348                 strcat(buf, ")");
17349         }
17350         for(i=1; i<=nrMoves; i++) { // copy last variation back
17351             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17352             for(j=0; j<MOVE_LEN; j++)
17353                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17354             for(j=0; j<2*MOVE_LEN; j++)
17355                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17356             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17357             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17358             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17359             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17360             commentList[currentMove+i] = commentList[framePtr+i];
17361             commentList[framePtr+i] = NULL;
17362         }
17363         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17364         framePtr = savedFramePtr[storedGames];
17365         gameInfo.result = savedResult[storedGames];
17366         if(gameInfo.resultDetails != NULL) {
17367             free(gameInfo.resultDetails);
17368       }
17369         gameInfo.resultDetails = savedDetails[storedGames];
17370         forwardMostMove = currentMove + nrMoves;
17371 }
17372
17373 Boolean
17374 PopTail (Boolean annotate)
17375 {
17376         if(appData.icsActive) return FALSE; // only in local mode
17377         if(!storedGames) return FALSE; // sanity
17378         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17379
17380         PopInner(annotate);
17381         if(currentMove < forwardMostMove) ForwardEvent(); else
17382         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17383
17384         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17385         return TRUE;
17386 }
17387
17388 void
17389 CleanupTail ()
17390 {       // remove all shelved variations
17391         int i;
17392         for(i=0; i<storedGames; i++) {
17393             if(savedDetails[i])
17394                 free(savedDetails[i]);
17395             savedDetails[i] = NULL;
17396         }
17397         for(i=framePtr; i<MAX_MOVES; i++) {
17398                 if(commentList[i]) free(commentList[i]);
17399                 commentList[i] = NULL;
17400         }
17401         framePtr = MAX_MOVES-1;
17402         storedGames = 0;
17403 }
17404
17405 void
17406 LoadVariation (int index, char *text)
17407 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17408         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17409         int level = 0, move;
17410
17411         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17412         // first find outermost bracketing variation
17413         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17414             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17415                 if(*p == '{') wait = '}'; else
17416                 if(*p == '[') wait = ']'; else
17417                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17418                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17419             }
17420             if(*p == wait) wait = NULLCHAR; // closing ]} found
17421             p++;
17422         }
17423         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17424         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17425         end[1] = NULLCHAR; // clip off comment beyond variation
17426         ToNrEvent(currentMove-1);
17427         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17428         // kludge: use ParsePV() to append variation to game
17429         move = currentMove;
17430         ParsePV(start, TRUE, TRUE);
17431         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17432         ClearPremoveHighlights();
17433         CommentPopDown();
17434         ToNrEvent(currentMove+1);
17435 }
17436