Redo seek graph with cairo
[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         /* Finish drag move */
7271         if (appData.highlightLastMove) {
7272             SetHighlights(fromX, fromY, toX, toY);
7273         } else {
7274             ClearHighlights();
7275         }
7276         DragPieceEnd(xPix, yPix); dragging = 0;
7277         /* Don't animate move and drag both */
7278         appData.animate = FALSE;
7279     }
7280     MarkTargetSquares(1);
7281
7282     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7283     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7284         ChessSquare piece = boards[currentMove][fromY][fromX];
7285         if(gameMode == EditPosition && piece != EmptySquare &&
7286            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7287             int n;
7288
7289             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7290                 n = PieceToNumber(piece - (int)BlackPawn);
7291                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7292                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7293                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7294             } else
7295             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7296                 n = PieceToNumber(piece);
7297                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7298                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7299                 boards[currentMove][n][BOARD_WIDTH-2]++;
7300             }
7301             boards[currentMove][fromY][fromX] = EmptySquare;
7302         }
7303         ClearHighlights();
7304         fromX = fromY = -1;
7305         DrawPosition(TRUE, boards[currentMove]);
7306         return;
7307     }
7308
7309     // off-board moves should not be highlighted
7310     if(x < 0 || y < 0) ClearHighlights();
7311
7312     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7313
7314     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7315         SetHighlights(fromX, fromY, toX, toY);
7316         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7317             // [HGM] super: promotion to captured piece selected from holdings
7318             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7319             promotionChoice = TRUE;
7320             // kludge follows to temporarily execute move on display, without promoting yet
7321             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7322             boards[currentMove][toY][toX] = p;
7323             DrawPosition(FALSE, boards[currentMove]);
7324             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7325             boards[currentMove][toY][toX] = q;
7326             DisplayMessage("Click in holdings to choose piece", "");
7327             return;
7328         }
7329         PromotionPopUp();
7330     } else {
7331         int oldMove = currentMove;
7332         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7333         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7334         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7335         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7336            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7337             DrawPosition(TRUE, boards[currentMove]);
7338         fromX = fromY = -1;
7339     }
7340     appData.animate = saveAnimate;
7341     if (appData.animate || appData.animateDragging) {
7342         /* Undo animation damage if needed */
7343         DrawPosition(FALSE, NULL);
7344     }
7345 }
7346
7347 int
7348 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7349 {   // front-end-free part taken out of PieceMenuPopup
7350     int whichMenu; int xSqr, ySqr;
7351
7352     if(seekGraphUp) { // [HGM] seekgraph
7353         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7354         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7355         return -2;
7356     }
7357
7358     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7359          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7360         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7361         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7362         if(action == Press)   {
7363             originalFlip = flipView;
7364             flipView = !flipView; // temporarily flip board to see game from partners perspective
7365             DrawPosition(TRUE, partnerBoard);
7366             DisplayMessage(partnerStatus, "");
7367             partnerUp = TRUE;
7368         } else if(action == Release) {
7369             flipView = originalFlip;
7370             DrawPosition(TRUE, boards[currentMove]);
7371             partnerUp = FALSE;
7372         }
7373         return -2;
7374     }
7375
7376     xSqr = EventToSquare(x, BOARD_WIDTH);
7377     ySqr = EventToSquare(y, BOARD_HEIGHT);
7378     if (action == Release) {
7379         if(pieceSweep != EmptySquare) {
7380             EditPositionMenuEvent(pieceSweep, toX, toY);
7381             pieceSweep = EmptySquare;
7382         } else UnLoadPV(); // [HGM] pv
7383     }
7384     if (action != Press) return -2; // return code to be ignored
7385     switch (gameMode) {
7386       case IcsExamining:
7387         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7388       case EditPosition:
7389         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7390         if (xSqr < 0 || ySqr < 0) return -1;
7391         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7392         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7393         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7394         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7395         NextPiece(0);
7396         return 2; // grab
7397       case IcsObserving:
7398         if(!appData.icsEngineAnalyze) return -1;
7399       case IcsPlayingWhite:
7400       case IcsPlayingBlack:
7401         if(!appData.zippyPlay) goto noZip;
7402       case AnalyzeMode:
7403       case AnalyzeFile:
7404       case MachinePlaysWhite:
7405       case MachinePlaysBlack:
7406       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7407         if (!appData.dropMenu) {
7408           LoadPV(x, y);
7409           return 2; // flag front-end to grab mouse events
7410         }
7411         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7412            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7413       case EditGame:
7414       noZip:
7415         if (xSqr < 0 || ySqr < 0) return -1;
7416         if (!appData.dropMenu || appData.testLegality &&
7417             gameInfo.variant != VariantBughouse &&
7418             gameInfo.variant != VariantCrazyhouse) return -1;
7419         whichMenu = 1; // drop menu
7420         break;
7421       default:
7422         return -1;
7423     }
7424
7425     if (((*fromX = xSqr) < 0) ||
7426         ((*fromY = ySqr) < 0)) {
7427         *fromX = *fromY = -1;
7428         return -1;
7429     }
7430     if (flipView)
7431       *fromX = BOARD_WIDTH - 1 - *fromX;
7432     else
7433       *fromY = BOARD_HEIGHT - 1 - *fromY;
7434
7435     return whichMenu;
7436 }
7437
7438 void
7439 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7440 {
7441 //    char * hint = lastHint;
7442     FrontEndProgramStats stats;
7443
7444     stats.which = cps == &first ? 0 : 1;
7445     stats.depth = cpstats->depth;
7446     stats.nodes = cpstats->nodes;
7447     stats.score = cpstats->score;
7448     stats.time = cpstats->time;
7449     stats.pv = cpstats->movelist;
7450     stats.hint = lastHint;
7451     stats.an_move_index = 0;
7452     stats.an_move_count = 0;
7453
7454     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7455         stats.hint = cpstats->move_name;
7456         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7457         stats.an_move_count = cpstats->nr_moves;
7458     }
7459
7460     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
7461
7462     SetProgramStats( &stats );
7463 }
7464
7465 void
7466 ClearEngineOutputPane (int which)
7467 {
7468     static FrontEndProgramStats dummyStats;
7469     dummyStats.which = which;
7470     dummyStats.pv = "#";
7471     SetProgramStats( &dummyStats );
7472 }
7473
7474 #define MAXPLAYERS 500
7475
7476 char *
7477 TourneyStandings (int display)
7478 {
7479     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7480     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7481     char result, *p, *names[MAXPLAYERS];
7482
7483     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7484         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7485     names[0] = p = strdup(appData.participants);
7486     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7487
7488     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7489
7490     while(result = appData.results[nr]) {
7491         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7492         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7493         wScore = bScore = 0;
7494         switch(result) {
7495           case '+': wScore = 2; break;
7496           case '-': bScore = 2; break;
7497           case '=': wScore = bScore = 1; break;
7498           case ' ':
7499           case '*': return strdup("busy"); // tourney not finished
7500         }
7501         score[w] += wScore;
7502         score[b] += bScore;
7503         games[w]++;
7504         games[b]++;
7505         nr++;
7506     }
7507     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7508     for(w=0; w<nPlayers; w++) {
7509         bScore = -1;
7510         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7511         ranking[w] = b; points[w] = bScore; score[b] = -2;
7512     }
7513     p = malloc(nPlayers*34+1);
7514     for(w=0; w<nPlayers && w<display; w++)
7515         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7516     free(names[0]);
7517     return p;
7518 }
7519
7520 void
7521 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7522 {       // count all piece types
7523         int p, f, r;
7524         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7525         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7526         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7527                 p = board[r][f];
7528                 pCnt[p]++;
7529                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7530                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7531                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7532                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7533                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7534                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7535         }
7536 }
7537
7538 int
7539 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7540 {
7541         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7542         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7543
7544         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7545         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7546         if(myPawns == 2 && nMine == 3) // KPP
7547             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7548         if(myPawns == 1 && nMine == 2) // KP
7549             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7550         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7551             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7552         if(myPawns) return FALSE;
7553         if(pCnt[WhiteRook+side])
7554             return pCnt[BlackRook-side] ||
7555                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7556                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7557                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7558         if(pCnt[WhiteCannon+side]) {
7559             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7560             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7561         }
7562         if(pCnt[WhiteKnight+side])
7563             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7564         return FALSE;
7565 }
7566
7567 int
7568 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7569 {
7570         VariantClass v = gameInfo.variant;
7571
7572         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7573         if(v == VariantShatranj) return TRUE; // always winnable through baring
7574         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7575         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7576
7577         if(v == VariantXiangqi) {
7578                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7579
7580                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7581                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7582                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7583                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7584                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7585                 if(stale) // we have at least one last-rank P plus perhaps C
7586                     return majors // KPKX
7587                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7588                 else // KCA*E*
7589                     return pCnt[WhiteFerz+side] // KCAK
7590                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7591                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7592                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7593
7594         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7595                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7596
7597                 if(nMine == 1) return FALSE; // bare King
7598                 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
7599                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7600                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7601                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7602                 if(pCnt[WhiteKnight+side])
7603                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7604                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7605                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7606                 if(nBishops)
7607                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7608                 if(pCnt[WhiteAlfil+side])
7609                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7610                 if(pCnt[WhiteWazir+side])
7611                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7612         }
7613
7614         return TRUE;
7615 }
7616
7617 int
7618 CompareWithRights (Board b1, Board b2)
7619 {
7620     int rights = 0;
7621     if(!CompareBoards(b1, b2)) return FALSE;
7622     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7623     /* compare castling rights */
7624     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7625            rights++; /* King lost rights, while rook still had them */
7626     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7627         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7628            rights++; /* but at least one rook lost them */
7629     }
7630     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7631            rights++;
7632     if( b1[CASTLING][5] != NoRights ) {
7633         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7634            rights++;
7635     }
7636     return rights == 0;
7637 }
7638
7639 int
7640 Adjudicate (ChessProgramState *cps)
7641 {       // [HGM] some adjudications useful with buggy engines
7642         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7643         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7644         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7645         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7646         int k, count = 0; static int bare = 1;
7647         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7648         Boolean canAdjudicate = !appData.icsActive;
7649
7650         // most tests only when we understand the game, i.e. legality-checking on
7651             if( appData.testLegality )
7652             {   /* [HGM] Some more adjudications for obstinate engines */
7653                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7654                 static int moveCount = 6;
7655                 ChessMove result;
7656                 char *reason = NULL;
7657
7658                 /* Count what is on board. */
7659                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7660
7661                 /* Some material-based adjudications that have to be made before stalemate test */
7662                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7663                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7664                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7665                      if(canAdjudicate && appData.checkMates) {
7666                          if(engineOpponent)
7667                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7668                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7669                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7670                          return 1;
7671                      }
7672                 }
7673
7674                 /* Bare King in Shatranj (loses) or Losers (wins) */
7675                 if( nrW == 1 || nrB == 1) {
7676                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7677                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7678                      if(canAdjudicate && appData.checkMates) {
7679                          if(engineOpponent)
7680                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7681                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7682                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7683                          return 1;
7684                      }
7685                   } else
7686                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7687                   {    /* bare King */
7688                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7689                         if(canAdjudicate && appData.checkMates) {
7690                             /* but only adjudicate if adjudication enabled */
7691                             if(engineOpponent)
7692                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7693                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7694                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7695                             return 1;
7696                         }
7697                   }
7698                 } else bare = 1;
7699
7700
7701             // don't wait for engine to announce game end if we can judge ourselves
7702             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7703               case MT_CHECK:
7704                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7705                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7706                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7707                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7708                             checkCnt++;
7709                         if(checkCnt >= 2) {
7710                             reason = "Xboard adjudication: 3rd check";
7711                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7712                             break;
7713                         }
7714                     }
7715                 }
7716               case MT_NONE:
7717               default:
7718                 break;
7719               case MT_STALEMATE:
7720               case MT_STAINMATE:
7721                 reason = "Xboard adjudication: Stalemate";
7722                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7723                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7724                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7725                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7726                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7727                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7728                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7729                                                                         EP_CHECKMATE : EP_WINS);
7730                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7731                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7732                 }
7733                 break;
7734               case MT_CHECKMATE:
7735                 reason = "Xboard adjudication: Checkmate";
7736                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7737                 break;
7738             }
7739
7740                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7741                     case EP_STALEMATE:
7742                         result = GameIsDrawn; break;
7743                     case EP_CHECKMATE:
7744                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7745                     case EP_WINS:
7746                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7747                     default:
7748                         result = EndOfFile;
7749                 }
7750                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7751                     if(engineOpponent)
7752                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7753                     GameEnds( result, reason, GE_XBOARD );
7754                     return 1;
7755                 }
7756
7757                 /* Next absolutely insufficient mating material. */
7758                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7759                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7760                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7761
7762                      /* always flag draws, for judging claims */
7763                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7764
7765                      if(canAdjudicate && appData.materialDraws) {
7766                          /* but only adjudicate them if adjudication enabled */
7767                          if(engineOpponent) {
7768                            SendToProgram("force\n", engineOpponent); // suppress reply
7769                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7770                          }
7771                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7772                          return 1;
7773                      }
7774                 }
7775
7776                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7777                 if(gameInfo.variant == VariantXiangqi ?
7778                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7779                  : nrW + nrB == 4 &&
7780                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7781                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7782                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7783                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7784                    ) ) {
7785                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7786                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7787                           if(engineOpponent) {
7788                             SendToProgram("force\n", engineOpponent); // suppress reply
7789                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7790                           }
7791                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7792                           return 1;
7793                      }
7794                 } else moveCount = 6;
7795             }
7796
7797         // Repetition draws and 50-move rule can be applied independently of legality testing
7798
7799                 /* Check for rep-draws */
7800                 count = 0;
7801                 for(k = forwardMostMove-2;
7802                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7803                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7804                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7805                     k-=2)
7806                 {   int rights=0;
7807                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7808                         /* compare castling rights */
7809                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7810                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7811                                 rights++; /* King lost rights, while rook still had them */
7812                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7813                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7814                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7815                                    rights++; /* but at least one rook lost them */
7816                         }
7817                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7818                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7819                                 rights++;
7820                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7821                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7822                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7823                                    rights++;
7824                         }
7825                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7826                             && appData.drawRepeats > 1) {
7827                              /* adjudicate after user-specified nr of repeats */
7828                              int result = GameIsDrawn;
7829                              char *details = "XBoard adjudication: repetition draw";
7830                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7831                                 // [HGM] xiangqi: check for forbidden perpetuals
7832                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7833                                 for(m=forwardMostMove; m>k; m-=2) {
7834                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7835                                         ourPerpetual = 0; // the current mover did not always check
7836                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7837                                         hisPerpetual = 0; // the opponent did not always check
7838                                 }
7839                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7840                                                                         ourPerpetual, hisPerpetual);
7841                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7842                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7843                                     details = "Xboard adjudication: perpetual checking";
7844                                 } else
7845                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7846                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7847                                 } else
7848                                 // Now check for perpetual chases
7849                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7850                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7851                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7852                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7853                                         static char resdet[MSG_SIZ];
7854                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7855                                         details = resdet;
7856                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7857                                     } else
7858                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7859                                         break; // Abort repetition-checking loop.
7860                                 }
7861                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7862                              }
7863                              if(engineOpponent) {
7864                                SendToProgram("force\n", engineOpponent); // suppress reply
7865                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7866                              }
7867                              GameEnds( result, details, GE_XBOARD );
7868                              return 1;
7869                         }
7870                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7871                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7872                     }
7873                 }
7874
7875                 /* Now we test for 50-move draws. Determine ply count */
7876                 count = forwardMostMove;
7877                 /* look for last irreversble move */
7878                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7879                     count--;
7880                 /* if we hit starting position, add initial plies */
7881                 if( count == backwardMostMove )
7882                     count -= initialRulePlies;
7883                 count = forwardMostMove - count;
7884                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7885                         // adjust reversible move counter for checks in Xiangqi
7886                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7887                         if(i < backwardMostMove) i = backwardMostMove;
7888                         while(i <= forwardMostMove) {
7889                                 lastCheck = inCheck; // check evasion does not count
7890                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7891                                 if(inCheck || lastCheck) count--; // check does not count
7892                                 i++;
7893                         }
7894                 }
7895                 if( count >= 100)
7896                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7897                          /* this is used to judge if draw claims are legal */
7898                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7899                          if(engineOpponent) {
7900                            SendToProgram("force\n", engineOpponent); // suppress reply
7901                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7902                          }
7903                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7904                          return 1;
7905                 }
7906
7907                 /* if draw offer is pending, treat it as a draw claim
7908                  * when draw condition present, to allow engines a way to
7909                  * claim draws before making their move to avoid a race
7910                  * condition occurring after their move
7911                  */
7912                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7913                          char *p = NULL;
7914                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7915                              p = "Draw claim: 50-move rule";
7916                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7917                              p = "Draw claim: 3-fold repetition";
7918                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7919                              p = "Draw claim: insufficient mating material";
7920                          if( p != NULL && canAdjudicate) {
7921                              if(engineOpponent) {
7922                                SendToProgram("force\n", engineOpponent); // suppress reply
7923                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7924                              }
7925                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7926                              return 1;
7927                          }
7928                 }
7929
7930                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7931                     if(engineOpponent) {
7932                       SendToProgram("force\n", engineOpponent); // suppress reply
7933                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7934                     }
7935                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7936                     return 1;
7937                 }
7938         return 0;
7939 }
7940
7941 char *
7942 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7943 {   // [HGM] book: this routine intercepts moves to simulate book replies
7944     char *bookHit = NULL;
7945
7946     //first determine if the incoming move brings opponent into his book
7947     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7948         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7949     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7950     if(bookHit != NULL && !cps->bookSuspend) {
7951         // make sure opponent is not going to reply after receiving move to book position
7952         SendToProgram("force\n", cps);
7953         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7954     }
7955     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7956     // now arrange restart after book miss
7957     if(bookHit) {
7958         // after a book hit we never send 'go', and the code after the call to this routine
7959         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7960         char buf[MSG_SIZ], *move = bookHit;
7961         if(cps->useSAN) {
7962             int fromX, fromY, toX, toY;
7963             char promoChar;
7964             ChessMove moveType;
7965             move = buf + 30;
7966             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7967                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7968                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7969                                     PosFlags(forwardMostMove),
7970                                     fromY, fromX, toY, toX, promoChar, move);
7971             } else {
7972                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7973                 bookHit = NULL;
7974             }
7975         }
7976         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7977         SendToProgram(buf, cps);
7978         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7979     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7980         SendToProgram("go\n", cps);
7981         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7982     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7983         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7984             SendToProgram("go\n", cps);
7985         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7986     }
7987     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7988 }
7989
7990 int
7991 LoadError (char *errmess, ChessProgramState *cps)
7992 {   // unloads engine and switches back to -ncp mode if it was first
7993     if(cps->initDone) return FALSE;
7994     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7995     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7996     cps->pr = NoProc; 
7997     if(cps == &first) {
7998         appData.noChessProgram = TRUE;
7999         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8000         gameMode = BeginningOfGame; ModeHighlight();
8001         SetNCPMode();
8002     }
8003     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8004     DisplayMessage("", ""); // erase waiting message
8005     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8006     return TRUE;
8007 }
8008
8009 char *savedMessage;
8010 ChessProgramState *savedState;
8011 void
8012 DeferredBookMove (void)
8013 {
8014         if(savedState->lastPing != savedState->lastPong)
8015                     ScheduleDelayedEvent(DeferredBookMove, 10);
8016         else
8017         HandleMachineMove(savedMessage, savedState);
8018 }
8019
8020 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8021
8022 void
8023 HandleMachineMove (char *message, ChessProgramState *cps)
8024 {
8025     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8026     char realname[MSG_SIZ];
8027     int fromX, fromY, toX, toY;
8028     ChessMove moveType;
8029     char promoChar;
8030     char *p, *pv=buf1;
8031     int machineWhite, oldError;
8032     char *bookHit;
8033
8034     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8035         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8036         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8037             DisplayError(_("Invalid pairing from pairing engine"), 0);
8038             return;
8039         }
8040         pairingReceived = 1;
8041         NextMatchGame();
8042         return; // Skim the pairing messages here.
8043     }
8044
8045     oldError = cps->userError; cps->userError = 0;
8046
8047 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8048     /*
8049      * Kludge to ignore BEL characters
8050      */
8051     while (*message == '\007') message++;
8052
8053     /*
8054      * [HGM] engine debug message: ignore lines starting with '#' character
8055      */
8056     if(cps->debug && *message == '#') return;
8057
8058     /*
8059      * Look for book output
8060      */
8061     if (cps == &first && bookRequested) {
8062         if (message[0] == '\t' || message[0] == ' ') {
8063             /* Part of the book output is here; append it */
8064             strcat(bookOutput, message);
8065             strcat(bookOutput, "  \n");
8066             return;
8067         } else if (bookOutput[0] != NULLCHAR) {
8068             /* All of book output has arrived; display it */
8069             char *p = bookOutput;
8070             while (*p != NULLCHAR) {
8071                 if (*p == '\t') *p = ' ';
8072                 p++;
8073             }
8074             DisplayInformation(bookOutput);
8075             bookRequested = FALSE;
8076             /* Fall through to parse the current output */
8077         }
8078     }
8079
8080     /*
8081      * Look for machine move.
8082      */
8083     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8084         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8085     {
8086         /* This method is only useful on engines that support ping */
8087         if (cps->lastPing != cps->lastPong) {
8088           if (gameMode == BeginningOfGame) {
8089             /* Extra move from before last new; ignore */
8090             if (appData.debugMode) {
8091                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8092             }
8093           } else {
8094             if (appData.debugMode) {
8095                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8096                         cps->which, gameMode);
8097             }
8098
8099             SendToProgram("undo\n", cps);
8100           }
8101           return;
8102         }
8103
8104         switch (gameMode) {
8105           case BeginningOfGame:
8106             /* Extra move from before last reset; ignore */
8107             if (appData.debugMode) {
8108                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8109             }
8110             return;
8111
8112           case EndOfGame:
8113           case IcsIdle:
8114           default:
8115             /* Extra move after we tried to stop.  The mode test is
8116                not a reliable way of detecting this problem, but it's
8117                the best we can do on engines that don't support ping.
8118             */
8119             if (appData.debugMode) {
8120                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8121                         cps->which, gameMode);
8122             }
8123             SendToProgram("undo\n", cps);
8124             return;
8125
8126           case MachinePlaysWhite:
8127           case IcsPlayingWhite:
8128             machineWhite = TRUE;
8129             break;
8130
8131           case MachinePlaysBlack:
8132           case IcsPlayingBlack:
8133             machineWhite = FALSE;
8134             break;
8135
8136           case TwoMachinesPlay:
8137             machineWhite = (cps->twoMachinesColor[0] == 'w');
8138             break;
8139         }
8140         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8141             if (appData.debugMode) {
8142                 fprintf(debugFP,
8143                         "Ignoring move out of turn by %s, gameMode %d"
8144                         ", forwardMost %d\n",
8145                         cps->which, gameMode, forwardMostMove);
8146             }
8147             return;
8148         }
8149
8150         if(cps->alphaRank) AlphaRank(machineMove, 4);
8151         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8152                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8153             /* Machine move could not be parsed; ignore it. */
8154           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8155                     machineMove, _(cps->which));
8156             DisplayError(buf1, 0);
8157             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8158                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8159             if (gameMode == TwoMachinesPlay) {
8160               GameEnds(machineWhite ? BlackWins : WhiteWins,
8161                        buf1, GE_XBOARD);
8162             }
8163             return;
8164         }
8165
8166         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8167         /* So we have to redo legality test with true e.p. status here,  */
8168         /* to make sure an illegal e.p. capture does not slip through,   */
8169         /* to cause a forfeit on a justified illegal-move complaint      */
8170         /* of the opponent.                                              */
8171         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8172            ChessMove moveType;
8173            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8174                              fromY, fromX, toY, toX, promoChar);
8175             if(moveType == IllegalMove) {
8176               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8177                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8178                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8179                            buf1, GE_XBOARD);
8180                 return;
8181            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8182            /* [HGM] Kludge to handle engines that send FRC-style castling
8183               when they shouldn't (like TSCP-Gothic) */
8184            switch(moveType) {
8185              case WhiteASideCastleFR:
8186              case BlackASideCastleFR:
8187                toX+=2;
8188                currentMoveString[2]++;
8189                break;
8190              case WhiteHSideCastleFR:
8191              case BlackHSideCastleFR:
8192                toX--;
8193                currentMoveString[2]--;
8194                break;
8195              default: ; // nothing to do, but suppresses warning of pedantic compilers
8196            }
8197         }
8198         hintRequested = FALSE;
8199         lastHint[0] = NULLCHAR;
8200         bookRequested = FALSE;
8201         /* Program may be pondering now */
8202         cps->maybeThinking = TRUE;
8203         if (cps->sendTime == 2) cps->sendTime = 1;
8204         if (cps->offeredDraw) cps->offeredDraw--;
8205
8206         /* [AS] Save move info*/
8207         pvInfoList[ forwardMostMove ].score = programStats.score;
8208         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8209         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8210
8211         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8212
8213         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8214         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8215             int count = 0;
8216
8217             while( count < adjudicateLossPlies ) {
8218                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8219
8220                 if( count & 1 ) {
8221                     score = -score; /* Flip score for winning side */
8222                 }
8223
8224                 if( score > adjudicateLossThreshold ) {
8225                     break;
8226                 }
8227
8228                 count++;
8229             }
8230
8231             if( count >= adjudicateLossPlies ) {
8232                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8233
8234                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8235                     "Xboard adjudication",
8236                     GE_XBOARD );
8237
8238                 return;
8239             }
8240         }
8241
8242         if(Adjudicate(cps)) {
8243             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8244             return; // [HGM] adjudicate: for all automatic game ends
8245         }
8246
8247 #if ZIPPY
8248         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8249             first.initDone) {
8250           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8251                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8252                 SendToICS("draw ");
8253                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8254           }
8255           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8256           ics_user_moved = 1;
8257           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8258                 char buf[3*MSG_SIZ];
8259
8260                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8261                         programStats.score / 100.,
8262                         programStats.depth,
8263                         programStats.time / 100.,
8264                         (unsigned int)programStats.nodes,
8265                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8266                         programStats.movelist);
8267                 SendToICS(buf);
8268 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8269           }
8270         }
8271 #endif
8272
8273         /* [AS] Clear stats for next move */
8274         ClearProgramStats();
8275         thinkOutput[0] = NULLCHAR;
8276         hiddenThinkOutputState = 0;
8277
8278         bookHit = NULL;
8279         if (gameMode == TwoMachinesPlay) {
8280             /* [HGM] relaying draw offers moved to after reception of move */
8281             /* and interpreting offer as claim if it brings draw condition */
8282             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8283                 SendToProgram("draw\n", cps->other);
8284             }
8285             if (cps->other->sendTime) {
8286                 SendTimeRemaining(cps->other,
8287                                   cps->other->twoMachinesColor[0] == 'w');
8288             }
8289             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8290             if (firstMove && !bookHit) {
8291                 firstMove = FALSE;
8292                 if (cps->other->useColors) {
8293                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8294                 }
8295                 SendToProgram("go\n", cps->other);
8296             }
8297             cps->other->maybeThinking = TRUE;
8298         }
8299
8300         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8301
8302         if (!pausing && appData.ringBellAfterMoves) {
8303             RingBell();
8304         }
8305
8306         /*
8307          * Reenable menu items that were disabled while
8308          * machine was thinking
8309          */
8310         if (gameMode != TwoMachinesPlay)
8311             SetUserThinkingEnables();
8312
8313         // [HGM] book: after book hit opponent has received move and is now in force mode
8314         // force the book reply into it, and then fake that it outputted this move by jumping
8315         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8316         if(bookHit) {
8317                 static char bookMove[MSG_SIZ]; // a bit generous?
8318
8319                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8320                 strcat(bookMove, bookHit);
8321                 message = bookMove;
8322                 cps = cps->other;
8323                 programStats.nodes = programStats.depth = programStats.time =
8324                 programStats.score = programStats.got_only_move = 0;
8325                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8326
8327                 if(cps->lastPing != cps->lastPong) {
8328                     savedMessage = message; // args for deferred call
8329                     savedState = cps;
8330                     ScheduleDelayedEvent(DeferredBookMove, 10);
8331                     return;
8332                 }
8333                 goto FakeBookMove;
8334         }
8335
8336         return;
8337     }
8338
8339     /* Set special modes for chess engines.  Later something general
8340      *  could be added here; for now there is just one kludge feature,
8341      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8342      *  when "xboard" is given as an interactive command.
8343      */
8344     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8345         cps->useSigint = FALSE;
8346         cps->useSigterm = FALSE;
8347     }
8348     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8349       ParseFeatures(message+8, cps);
8350       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8351     }
8352
8353     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8354                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8355       int dummy, s=6; char buf[MSG_SIZ];
8356       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8357       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8358       if(startedFromSetupPosition) return;
8359       ParseFEN(boards[0], &dummy, message+s);
8360       DrawPosition(TRUE, boards[0]);
8361       startedFromSetupPosition = TRUE;
8362       return;
8363     }
8364     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8365      * want this, I was asked to put it in, and obliged.
8366      */
8367     if (!strncmp(message, "setboard ", 9)) {
8368         Board initial_position;
8369
8370         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8371
8372         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8373             DisplayError(_("Bad FEN received from engine"), 0);
8374             return ;
8375         } else {
8376            Reset(TRUE, FALSE);
8377            CopyBoard(boards[0], initial_position);
8378            initialRulePlies = FENrulePlies;
8379            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8380            else gameMode = MachinePlaysBlack;
8381            DrawPosition(FALSE, boards[currentMove]);
8382         }
8383         return;
8384     }
8385
8386     /*
8387      * Look for communication commands
8388      */
8389     if (!strncmp(message, "telluser ", 9)) {
8390         if(message[9] == '\\' && message[10] == '\\')
8391             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8392         PlayTellSound();
8393         DisplayNote(message + 9);
8394         return;
8395     }
8396     if (!strncmp(message, "tellusererror ", 14)) {
8397         cps->userError = 1;
8398         if(message[14] == '\\' && message[15] == '\\')
8399             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8400         PlayTellSound();
8401         DisplayError(message + 14, 0);
8402         return;
8403     }
8404     if (!strncmp(message, "tellopponent ", 13)) {
8405       if (appData.icsActive) {
8406         if (loggedOn) {
8407           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8408           SendToICS(buf1);
8409         }
8410       } else {
8411         DisplayNote(message + 13);
8412       }
8413       return;
8414     }
8415     if (!strncmp(message, "tellothers ", 11)) {
8416       if (appData.icsActive) {
8417         if (loggedOn) {
8418           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8419           SendToICS(buf1);
8420         }
8421       }
8422       return;
8423     }
8424     if (!strncmp(message, "tellall ", 8)) {
8425       if (appData.icsActive) {
8426         if (loggedOn) {
8427           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8428           SendToICS(buf1);
8429         }
8430       } else {
8431         DisplayNote(message + 8);
8432       }
8433       return;
8434     }
8435     if (strncmp(message, "warning", 7) == 0) {
8436         /* Undocumented feature, use tellusererror in new code */
8437         DisplayError(message, 0);
8438         return;
8439     }
8440     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8441         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8442         strcat(realname, " query");
8443         AskQuestion(realname, buf2, buf1, cps->pr);
8444         return;
8445     }
8446     /* Commands from the engine directly to ICS.  We don't allow these to be
8447      *  sent until we are logged on. Crafty kibitzes have been known to
8448      *  interfere with the login process.
8449      */
8450     if (loggedOn) {
8451         if (!strncmp(message, "tellics ", 8)) {
8452             SendToICS(message + 8);
8453             SendToICS("\n");
8454             return;
8455         }
8456         if (!strncmp(message, "tellicsnoalias ", 15)) {
8457             SendToICS(ics_prefix);
8458             SendToICS(message + 15);
8459             SendToICS("\n");
8460             return;
8461         }
8462         /* The following are for backward compatibility only */
8463         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8464             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8465             SendToICS(ics_prefix);
8466             SendToICS(message);
8467             SendToICS("\n");
8468             return;
8469         }
8470     }
8471     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8472         return;
8473     }
8474     /*
8475      * If the move is illegal, cancel it and redraw the board.
8476      * Also deal with other error cases.  Matching is rather loose
8477      * here to accommodate engines written before the spec.
8478      */
8479     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8480         strncmp(message, "Error", 5) == 0) {
8481         if (StrStr(message, "name") ||
8482             StrStr(message, "rating") || StrStr(message, "?") ||
8483             StrStr(message, "result") || StrStr(message, "board") ||
8484             StrStr(message, "bk") || StrStr(message, "computer") ||
8485             StrStr(message, "variant") || StrStr(message, "hint") ||
8486             StrStr(message, "random") || StrStr(message, "depth") ||
8487             StrStr(message, "accepted")) {
8488             return;
8489         }
8490         if (StrStr(message, "protover")) {
8491           /* Program is responding to input, so it's apparently done
8492              initializing, and this error message indicates it is
8493              protocol version 1.  So we don't need to wait any longer
8494              for it to initialize and send feature commands. */
8495           FeatureDone(cps, 1);
8496           cps->protocolVersion = 1;
8497           return;
8498         }
8499         cps->maybeThinking = FALSE;
8500
8501         if (StrStr(message, "draw")) {
8502             /* Program doesn't have "draw" command */
8503             cps->sendDrawOffers = 0;
8504             return;
8505         }
8506         if (cps->sendTime != 1 &&
8507             (StrStr(message, "time") || StrStr(message, "otim"))) {
8508           /* Program apparently doesn't have "time" or "otim" command */
8509           cps->sendTime = 0;
8510           return;
8511         }
8512         if (StrStr(message, "analyze")) {
8513             cps->analysisSupport = FALSE;
8514             cps->analyzing = FALSE;
8515 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8516             EditGameEvent(); // [HGM] try to preserve loaded game
8517             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8518             DisplayError(buf2, 0);
8519             return;
8520         }
8521         if (StrStr(message, "(no matching move)st")) {
8522           /* Special kludge for GNU Chess 4 only */
8523           cps->stKludge = TRUE;
8524           SendTimeControl(cps, movesPerSession, timeControl,
8525                           timeIncrement, appData.searchDepth,
8526                           searchTime);
8527           return;
8528         }
8529         if (StrStr(message, "(no matching move)sd")) {
8530           /* Special kludge for GNU Chess 4 only */
8531           cps->sdKludge = TRUE;
8532           SendTimeControl(cps, movesPerSession, timeControl,
8533                           timeIncrement, appData.searchDepth,
8534                           searchTime);
8535           return;
8536         }
8537         if (!StrStr(message, "llegal")) {
8538             return;
8539         }
8540         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8541             gameMode == IcsIdle) return;
8542         if (forwardMostMove <= backwardMostMove) return;
8543         if (pausing) PauseEvent();
8544       if(appData.forceIllegal) {
8545             // [HGM] illegal: machine refused move; force position after move into it
8546           SendToProgram("force\n", cps);
8547           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8548                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8549                 // when black is to move, while there might be nothing on a2 or black
8550                 // might already have the move. So send the board as if white has the move.
8551                 // But first we must change the stm of the engine, as it refused the last move
8552                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8553                 if(WhiteOnMove(forwardMostMove)) {
8554                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8555                     SendBoard(cps, forwardMostMove); // kludgeless board
8556                 } else {
8557                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8558                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8559                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8560                 }
8561           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8562             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8563                  gameMode == TwoMachinesPlay)
8564               SendToProgram("go\n", cps);
8565             return;
8566       } else
8567         if (gameMode == PlayFromGameFile) {
8568             /* Stop reading this game file */
8569             gameMode = EditGame;
8570             ModeHighlight();
8571         }
8572         /* [HGM] illegal-move claim should forfeit game when Xboard */
8573         /* only passes fully legal moves                            */
8574         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8575             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8576                                 "False illegal-move claim", GE_XBOARD );
8577             return; // do not take back move we tested as valid
8578         }
8579         currentMove = forwardMostMove-1;
8580         DisplayMove(currentMove-1); /* before DisplayMoveError */
8581         SwitchClocks(forwardMostMove-1); // [HGM] race
8582         DisplayBothClocks();
8583         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8584                 parseList[currentMove], _(cps->which));
8585         DisplayMoveError(buf1);
8586         DrawPosition(FALSE, boards[currentMove]);
8587
8588         SetUserThinkingEnables();
8589         return;
8590     }
8591     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8592         /* Program has a broken "time" command that
8593            outputs a string not ending in newline.
8594            Don't use it. */
8595         cps->sendTime = 0;
8596     }
8597
8598     /*
8599      * If chess program startup fails, exit with an error message.
8600      * Attempts to recover here are futile. [HGM] Well, we try anyway
8601      */
8602     if ((StrStr(message, "unknown host") != NULL)
8603         || (StrStr(message, "No remote directory") != NULL)
8604         || (StrStr(message, "not found") != NULL)
8605         || (StrStr(message, "No such file") != NULL)
8606         || (StrStr(message, "can't alloc") != NULL)
8607         || (StrStr(message, "Permission denied") != NULL)) {
8608
8609         cps->maybeThinking = FALSE;
8610         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8611                 _(cps->which), cps->program, cps->host, message);
8612         RemoveInputSource(cps->isr);
8613         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8614             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8615             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8616         }
8617         return;
8618     }
8619
8620     /*
8621      * Look for hint output
8622      */
8623     if (sscanf(message, "Hint: %s", buf1) == 1) {
8624         if (cps == &first && hintRequested) {
8625             hintRequested = FALSE;
8626             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8627                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8628                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8629                                     PosFlags(forwardMostMove),
8630                                     fromY, fromX, toY, toX, promoChar, buf1);
8631                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8632                 DisplayInformation(buf2);
8633             } else {
8634                 /* Hint move could not be parsed!? */
8635               snprintf(buf2, sizeof(buf2),
8636                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8637                         buf1, _(cps->which));
8638                 DisplayError(buf2, 0);
8639             }
8640         } else {
8641           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8642         }
8643         return;
8644     }
8645
8646     /*
8647      * Ignore other messages if game is not in progress
8648      */
8649     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8650         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8651
8652     /*
8653      * look for win, lose, draw, or draw offer
8654      */
8655     if (strncmp(message, "1-0", 3) == 0) {
8656         char *p, *q, *r = "";
8657         p = strchr(message, '{');
8658         if (p) {
8659             q = strchr(p, '}');
8660             if (q) {
8661                 *q = NULLCHAR;
8662                 r = p + 1;
8663             }
8664         }
8665         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8666         return;
8667     } else if (strncmp(message, "0-1", 3) == 0) {
8668         char *p, *q, *r = "";
8669         p = strchr(message, '{');
8670         if (p) {
8671             q = strchr(p, '}');
8672             if (q) {
8673                 *q = NULLCHAR;
8674                 r = p + 1;
8675             }
8676         }
8677         /* Kludge for Arasan 4.1 bug */
8678         if (strcmp(r, "Black resigns") == 0) {
8679             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8680             return;
8681         }
8682         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8683         return;
8684     } else if (strncmp(message, "1/2", 3) == 0) {
8685         char *p, *q, *r = "";
8686         p = strchr(message, '{');
8687         if (p) {
8688             q = strchr(p, '}');
8689             if (q) {
8690                 *q = NULLCHAR;
8691                 r = p + 1;
8692             }
8693         }
8694
8695         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8696         return;
8697
8698     } else if (strncmp(message, "White resign", 12) == 0) {
8699         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8700         return;
8701     } else if (strncmp(message, "Black resign", 12) == 0) {
8702         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8703         return;
8704     } else if (strncmp(message, "White matches", 13) == 0 ||
8705                strncmp(message, "Black matches", 13) == 0   ) {
8706         /* [HGM] ignore GNUShogi noises */
8707         return;
8708     } else if (strncmp(message, "White", 5) == 0 &&
8709                message[5] != '(' &&
8710                StrStr(message, "Black") == NULL) {
8711         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8712         return;
8713     } else if (strncmp(message, "Black", 5) == 0 &&
8714                message[5] != '(') {
8715         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8716         return;
8717     } else if (strcmp(message, "resign") == 0 ||
8718                strcmp(message, "computer resigns") == 0) {
8719         switch (gameMode) {
8720           case MachinePlaysBlack:
8721           case IcsPlayingBlack:
8722             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8723             break;
8724           case MachinePlaysWhite:
8725           case IcsPlayingWhite:
8726             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8727             break;
8728           case TwoMachinesPlay:
8729             if (cps->twoMachinesColor[0] == 'w')
8730               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8731             else
8732               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8733             break;
8734           default:
8735             /* can't happen */
8736             break;
8737         }
8738         return;
8739     } else if (strncmp(message, "opponent mates", 14) == 0) {
8740         switch (gameMode) {
8741           case MachinePlaysBlack:
8742           case IcsPlayingBlack:
8743             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8744             break;
8745           case MachinePlaysWhite:
8746           case IcsPlayingWhite:
8747             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8748             break;
8749           case TwoMachinesPlay:
8750             if (cps->twoMachinesColor[0] == 'w')
8751               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8752             else
8753               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8754             break;
8755           default:
8756             /* can't happen */
8757             break;
8758         }
8759         return;
8760     } else if (strncmp(message, "computer mates", 14) == 0) {
8761         switch (gameMode) {
8762           case MachinePlaysBlack:
8763           case IcsPlayingBlack:
8764             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8765             break;
8766           case MachinePlaysWhite:
8767           case IcsPlayingWhite:
8768             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8769             break;
8770           case TwoMachinesPlay:
8771             if (cps->twoMachinesColor[0] == 'w')
8772               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8773             else
8774               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8775             break;
8776           default:
8777             /* can't happen */
8778             break;
8779         }
8780         return;
8781     } else if (strncmp(message, "checkmate", 9) == 0) {
8782         if (WhiteOnMove(forwardMostMove)) {
8783             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8784         } else {
8785             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8786         }
8787         return;
8788     } else if (strstr(message, "Draw") != NULL ||
8789                strstr(message, "game is a draw") != NULL) {
8790         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8791         return;
8792     } else if (strstr(message, "offer") != NULL &&
8793                strstr(message, "draw") != NULL) {
8794 #if ZIPPY
8795         if (appData.zippyPlay && first.initDone) {
8796             /* Relay offer to ICS */
8797             SendToICS(ics_prefix);
8798             SendToICS("draw\n");
8799         }
8800 #endif
8801         cps->offeredDraw = 2; /* valid until this engine moves twice */
8802         if (gameMode == TwoMachinesPlay) {
8803             if (cps->other->offeredDraw) {
8804                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8805             /* [HGM] in two-machine mode we delay relaying draw offer      */
8806             /* until after we also have move, to see if it is really claim */
8807             }
8808         } else if (gameMode == MachinePlaysWhite ||
8809                    gameMode == MachinePlaysBlack) {
8810           if (userOfferedDraw) {
8811             DisplayInformation(_("Machine accepts your draw offer"));
8812             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8813           } else {
8814             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8815           }
8816         }
8817     }
8818
8819
8820     /*
8821      * Look for thinking output
8822      */
8823     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8824           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8825                                 ) {
8826         int plylev, mvleft, mvtot, curscore, time;
8827         char mvname[MOVE_LEN];
8828         u64 nodes; // [DM]
8829         char plyext;
8830         int ignore = FALSE;
8831         int prefixHint = FALSE;
8832         mvname[0] = NULLCHAR;
8833
8834         switch (gameMode) {
8835           case MachinePlaysBlack:
8836           case IcsPlayingBlack:
8837             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8838             break;
8839           case MachinePlaysWhite:
8840           case IcsPlayingWhite:
8841             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8842             break;
8843           case AnalyzeMode:
8844           case AnalyzeFile:
8845             break;
8846           case IcsObserving: /* [DM] icsEngineAnalyze */
8847             if (!appData.icsEngineAnalyze) ignore = TRUE;
8848             break;
8849           case TwoMachinesPlay:
8850             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8851                 ignore = TRUE;
8852             }
8853             break;
8854           default:
8855             ignore = TRUE;
8856             break;
8857         }
8858
8859         if (!ignore) {
8860             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8861             buf1[0] = NULLCHAR;
8862             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8863                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8864
8865                 if (plyext != ' ' && plyext != '\t') {
8866                     time *= 100;
8867                 }
8868
8869                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8870                 if( cps->scoreIsAbsolute &&
8871                     ( gameMode == MachinePlaysBlack ||
8872                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8873                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8874                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8875                      !WhiteOnMove(currentMove)
8876                     ) )
8877                 {
8878                     curscore = -curscore;
8879                 }
8880
8881                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8882
8883                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8884                         char buf[MSG_SIZ];
8885                         FILE *f;
8886                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8887                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8888                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8889                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8890                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8891                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8892                                 fclose(f);
8893                         } else DisplayError(_("failed writing PV"), 0);
8894                 }
8895
8896                 tempStats.depth = plylev;
8897                 tempStats.nodes = nodes;
8898                 tempStats.time = time;
8899                 tempStats.score = curscore;
8900                 tempStats.got_only_move = 0;
8901
8902                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8903                         int ticklen;
8904
8905                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8906                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8907                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8908                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8909                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8910                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8911                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8912                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8913                 }
8914
8915                 /* Buffer overflow protection */
8916                 if (pv[0] != NULLCHAR) {
8917                     if (strlen(pv) >= sizeof(tempStats.movelist)
8918                         && appData.debugMode) {
8919                         fprintf(debugFP,
8920                                 "PV is too long; using the first %u bytes.\n",
8921                                 (unsigned) sizeof(tempStats.movelist) - 1);
8922                     }
8923
8924                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8925                 } else {
8926                     sprintf(tempStats.movelist, " no PV\n");
8927                 }
8928
8929                 if (tempStats.seen_stat) {
8930                     tempStats.ok_to_send = 1;
8931                 }
8932
8933                 if (strchr(tempStats.movelist, '(') != NULL) {
8934                     tempStats.line_is_book = 1;
8935                     tempStats.nr_moves = 0;
8936                     tempStats.moves_left = 0;
8937                 } else {
8938                     tempStats.line_is_book = 0;
8939                 }
8940
8941                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8942                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8943
8944                 SendProgramStatsToFrontend( cps, &tempStats );
8945
8946                 /*
8947                     [AS] Protect the thinkOutput buffer from overflow... this
8948                     is only useful if buf1 hasn't overflowed first!
8949                 */
8950                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8951                          plylev,
8952                          (gameMode == TwoMachinesPlay ?
8953                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8954                          ((double) curscore) / 100.0,
8955                          prefixHint ? lastHint : "",
8956                          prefixHint ? " " : "" );
8957
8958                 if( buf1[0] != NULLCHAR ) {
8959                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8960
8961                     if( strlen(pv) > max_len ) {
8962                         if( appData.debugMode) {
8963                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8964                         }
8965                         pv[max_len+1] = '\0';
8966                     }
8967
8968                     strcat( thinkOutput, pv);
8969                 }
8970
8971                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8972                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8973                     DisplayMove(currentMove - 1);
8974                 }
8975                 return;
8976
8977             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8978                 /* crafty (9.25+) says "(only move) <move>"
8979                  * if there is only 1 legal move
8980                  */
8981                 sscanf(p, "(only move) %s", buf1);
8982                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8983                 sprintf(programStats.movelist, "%s (only move)", buf1);
8984                 programStats.depth = 1;
8985                 programStats.nr_moves = 1;
8986                 programStats.moves_left = 1;
8987                 programStats.nodes = 1;
8988                 programStats.time = 1;
8989                 programStats.got_only_move = 1;
8990
8991                 /* Not really, but we also use this member to
8992                    mean "line isn't going to change" (Crafty
8993                    isn't searching, so stats won't change) */
8994                 programStats.line_is_book = 1;
8995
8996                 SendProgramStatsToFrontend( cps, &programStats );
8997
8998                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8999                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9000                     DisplayMove(currentMove - 1);
9001                 }
9002                 return;
9003             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9004                               &time, &nodes, &plylev, &mvleft,
9005                               &mvtot, mvname) >= 5) {
9006                 /* The stat01: line is from Crafty (9.29+) in response
9007                    to the "." command */
9008                 programStats.seen_stat = 1;
9009                 cps->maybeThinking = TRUE;
9010
9011                 if (programStats.got_only_move || !appData.periodicUpdates)
9012                   return;
9013
9014                 programStats.depth = plylev;
9015                 programStats.time = time;
9016                 programStats.nodes = nodes;
9017                 programStats.moves_left = mvleft;
9018                 programStats.nr_moves = mvtot;
9019                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9020                 programStats.ok_to_send = 1;
9021                 programStats.movelist[0] = '\0';
9022
9023                 SendProgramStatsToFrontend( cps, &programStats );
9024
9025                 return;
9026
9027             } else if (strncmp(message,"++",2) == 0) {
9028                 /* Crafty 9.29+ outputs this */
9029                 programStats.got_fail = 2;
9030                 return;
9031
9032             } else if (strncmp(message,"--",2) == 0) {
9033                 /* Crafty 9.29+ outputs this */
9034                 programStats.got_fail = 1;
9035                 return;
9036
9037             } else if (thinkOutput[0] != NULLCHAR &&
9038                        strncmp(message, "    ", 4) == 0) {
9039                 unsigned message_len;
9040
9041                 p = message;
9042                 while (*p && *p == ' ') p++;
9043
9044                 message_len = strlen( p );
9045
9046                 /* [AS] Avoid buffer overflow */
9047                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9048                     strcat(thinkOutput, " ");
9049                     strcat(thinkOutput, p);
9050                 }
9051
9052                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9053                     strcat(programStats.movelist, " ");
9054                     strcat(programStats.movelist, p);
9055                 }
9056
9057                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9058                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9059                     DisplayMove(currentMove - 1);
9060                 }
9061                 return;
9062             }
9063         }
9064         else {
9065             buf1[0] = NULLCHAR;
9066
9067             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9068                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9069             {
9070                 ChessProgramStats cpstats;
9071
9072                 if (plyext != ' ' && plyext != '\t') {
9073                     time *= 100;
9074                 }
9075
9076                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9077                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9078                     curscore = -curscore;
9079                 }
9080
9081                 cpstats.depth = plylev;
9082                 cpstats.nodes = nodes;
9083                 cpstats.time = time;
9084                 cpstats.score = curscore;
9085                 cpstats.got_only_move = 0;
9086                 cpstats.movelist[0] = '\0';
9087
9088                 if (buf1[0] != NULLCHAR) {
9089                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9090                 }
9091
9092                 cpstats.ok_to_send = 0;
9093                 cpstats.line_is_book = 0;
9094                 cpstats.nr_moves = 0;
9095                 cpstats.moves_left = 0;
9096
9097                 SendProgramStatsToFrontend( cps, &cpstats );
9098             }
9099         }
9100     }
9101 }
9102
9103
9104 /* Parse a game score from the character string "game", and
9105    record it as the history of the current game.  The game
9106    score is NOT assumed to start from the standard position.
9107    The display is not updated in any way.
9108    */
9109 void
9110 ParseGameHistory (char *game)
9111 {
9112     ChessMove moveType;
9113     int fromX, fromY, toX, toY, boardIndex;
9114     char promoChar;
9115     char *p, *q;
9116     char buf[MSG_SIZ];
9117
9118     if (appData.debugMode)
9119       fprintf(debugFP, "Parsing game history: %s\n", game);
9120
9121     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9122     gameInfo.site = StrSave(appData.icsHost);
9123     gameInfo.date = PGNDate();
9124     gameInfo.round = StrSave("-");
9125
9126     /* Parse out names of players */
9127     while (*game == ' ') game++;
9128     p = buf;
9129     while (*game != ' ') *p++ = *game++;
9130     *p = NULLCHAR;
9131     gameInfo.white = StrSave(buf);
9132     while (*game == ' ') game++;
9133     p = buf;
9134     while (*game != ' ' && *game != '\n') *p++ = *game++;
9135     *p = NULLCHAR;
9136     gameInfo.black = StrSave(buf);
9137
9138     /* Parse moves */
9139     boardIndex = blackPlaysFirst ? 1 : 0;
9140     yynewstr(game);
9141     for (;;) {
9142         yyboardindex = boardIndex;
9143         moveType = (ChessMove) Myylex();
9144         switch (moveType) {
9145           case IllegalMove:             /* maybe suicide chess, etc. */
9146   if (appData.debugMode) {
9147     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9148     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9149     setbuf(debugFP, NULL);
9150   }
9151           case WhitePromotion:
9152           case BlackPromotion:
9153           case WhiteNonPromotion:
9154           case BlackNonPromotion:
9155           case NormalMove:
9156           case WhiteCapturesEnPassant:
9157           case BlackCapturesEnPassant:
9158           case WhiteKingSideCastle:
9159           case WhiteQueenSideCastle:
9160           case BlackKingSideCastle:
9161           case BlackQueenSideCastle:
9162           case WhiteKingSideCastleWild:
9163           case WhiteQueenSideCastleWild:
9164           case BlackKingSideCastleWild:
9165           case BlackQueenSideCastleWild:
9166           /* PUSH Fabien */
9167           case WhiteHSideCastleFR:
9168           case WhiteASideCastleFR:
9169           case BlackHSideCastleFR:
9170           case BlackASideCastleFR:
9171           /* POP Fabien */
9172             fromX = currentMoveString[0] - AAA;
9173             fromY = currentMoveString[1] - ONE;
9174             toX = currentMoveString[2] - AAA;
9175             toY = currentMoveString[3] - ONE;
9176             promoChar = currentMoveString[4];
9177             break;
9178           case WhiteDrop:
9179           case BlackDrop:
9180             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9181             fromX = moveType == WhiteDrop ?
9182               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9183             (int) CharToPiece(ToLower(currentMoveString[0]));
9184             fromY = DROP_RANK;
9185             toX = currentMoveString[2] - AAA;
9186             toY = currentMoveString[3] - ONE;
9187             promoChar = NULLCHAR;
9188             break;
9189           case AmbiguousMove:
9190             /* bug? */
9191             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9192   if (appData.debugMode) {
9193     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9194     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9195     setbuf(debugFP, NULL);
9196   }
9197             DisplayError(buf, 0);
9198             return;
9199           case ImpossibleMove:
9200             /* bug? */
9201             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9202   if (appData.debugMode) {
9203     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9204     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9205     setbuf(debugFP, NULL);
9206   }
9207             DisplayError(buf, 0);
9208             return;
9209           case EndOfFile:
9210             if (boardIndex < backwardMostMove) {
9211                 /* Oops, gap.  How did that happen? */
9212                 DisplayError(_("Gap in move list"), 0);
9213                 return;
9214             }
9215             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9216             if (boardIndex > forwardMostMove) {
9217                 forwardMostMove = boardIndex;
9218             }
9219             return;
9220           case ElapsedTime:
9221             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9222                 strcat(parseList[boardIndex-1], " ");
9223                 strcat(parseList[boardIndex-1], yy_text);
9224             }
9225             continue;
9226           case Comment:
9227           case PGNTag:
9228           case NAG:
9229           default:
9230             /* ignore */
9231             continue;
9232           case WhiteWins:
9233           case BlackWins:
9234           case GameIsDrawn:
9235           case GameUnfinished:
9236             if (gameMode == IcsExamining) {
9237                 if (boardIndex < backwardMostMove) {
9238                     /* Oops, gap.  How did that happen? */
9239                     return;
9240                 }
9241                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9242                 return;
9243             }
9244             gameInfo.result = moveType;
9245             p = strchr(yy_text, '{');
9246             if (p == NULL) p = strchr(yy_text, '(');
9247             if (p == NULL) {
9248                 p = yy_text;
9249                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9250             } else {
9251                 q = strchr(p, *p == '{' ? '}' : ')');
9252                 if (q != NULL) *q = NULLCHAR;
9253                 p++;
9254             }
9255             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9256             gameInfo.resultDetails = StrSave(p);
9257             continue;
9258         }
9259         if (boardIndex >= forwardMostMove &&
9260             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9261             backwardMostMove = blackPlaysFirst ? 1 : 0;
9262             return;
9263         }
9264         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9265                                  fromY, fromX, toY, toX, promoChar,
9266                                  parseList[boardIndex]);
9267         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9268         /* currentMoveString is set as a side-effect of yylex */
9269         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9270         strcat(moveList[boardIndex], "\n");
9271         boardIndex++;
9272         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9273         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9274           case MT_NONE:
9275           case MT_STALEMATE:
9276           default:
9277             break;
9278           case MT_CHECK:
9279             if(gameInfo.variant != VariantShogi)
9280                 strcat(parseList[boardIndex - 1], "+");
9281             break;
9282           case MT_CHECKMATE:
9283           case MT_STAINMATE:
9284             strcat(parseList[boardIndex - 1], "#");
9285             break;
9286         }
9287     }
9288 }
9289
9290
9291 /* Apply a move to the given board  */
9292 void
9293 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9294 {
9295   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9296   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9297
9298     /* [HGM] compute & store e.p. status and castling rights for new position */
9299     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9300
9301       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9302       oldEP = (signed char)board[EP_STATUS];
9303       board[EP_STATUS] = EP_NONE;
9304
9305   if (fromY == DROP_RANK) {
9306         /* must be first */
9307         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9308             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9309             return;
9310         }
9311         piece = board[toY][toX] = (ChessSquare) fromX;
9312   } else {
9313       int i;
9314
9315       if( board[toY][toX] != EmptySquare )
9316            board[EP_STATUS] = EP_CAPTURE;
9317
9318       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9319            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9320                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9321       } else
9322       if( board[fromY][fromX] == WhitePawn ) {
9323            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9324                board[EP_STATUS] = EP_PAWN_MOVE;
9325            if( toY-fromY==2) {
9326                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9327                         gameInfo.variant != VariantBerolina || toX < fromX)
9328                       board[EP_STATUS] = toX | berolina;
9329                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9330                         gameInfo.variant != VariantBerolina || toX > fromX)
9331                       board[EP_STATUS] = toX;
9332            }
9333       } else
9334       if( board[fromY][fromX] == BlackPawn ) {
9335            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9336                board[EP_STATUS] = EP_PAWN_MOVE;
9337            if( toY-fromY== -2) {
9338                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9339                         gameInfo.variant != VariantBerolina || toX < fromX)
9340                       board[EP_STATUS] = toX | berolina;
9341                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9342                         gameInfo.variant != VariantBerolina || toX > fromX)
9343                       board[EP_STATUS] = toX;
9344            }
9345        }
9346
9347        for(i=0; i<nrCastlingRights; i++) {
9348            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9349               board[CASTLING][i] == toX   && castlingRank[i] == toY
9350              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9351        }
9352
9353        if(gameInfo.variant == VariantSChess) { // update virginity
9354            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9355            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9356            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9357            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9358        }
9359
9360      if (fromX == toX && fromY == toY) return;
9361
9362      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9363      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9364      if(gameInfo.variant == VariantKnightmate)
9365          king += (int) WhiteUnicorn - (int) WhiteKing;
9366
9367     /* Code added by Tord: */
9368     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9369     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9370         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9371       board[fromY][fromX] = EmptySquare;
9372       board[toY][toX] = EmptySquare;
9373       if((toX > fromX) != (piece == WhiteRook)) {
9374         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9375       } else {
9376         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9377       }
9378     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9379                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9380       board[fromY][fromX] = EmptySquare;
9381       board[toY][toX] = EmptySquare;
9382       if((toX > fromX) != (piece == BlackRook)) {
9383         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9384       } else {
9385         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9386       }
9387     /* End of code added by Tord */
9388
9389     } else if (board[fromY][fromX] == king
9390         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9391         && toY == fromY && toX > fromX+1) {
9392         board[fromY][fromX] = EmptySquare;
9393         board[toY][toX] = king;
9394         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9395         board[fromY][BOARD_RGHT-1] = EmptySquare;
9396     } else if (board[fromY][fromX] == king
9397         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9398                && toY == fromY && toX < fromX-1) {
9399         board[fromY][fromX] = EmptySquare;
9400         board[toY][toX] = king;
9401         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9402         board[fromY][BOARD_LEFT] = EmptySquare;
9403     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9404                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9405                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9406                ) {
9407         /* white pawn promotion */
9408         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9409         if(gameInfo.variant==VariantBughouse ||
9410            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9411             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9412         board[fromY][fromX] = EmptySquare;
9413     } else if ((fromY >= BOARD_HEIGHT>>1)
9414                && (toX != fromX)
9415                && gameInfo.variant != VariantXiangqi
9416                && gameInfo.variant != VariantBerolina
9417                && (board[fromY][fromX] == WhitePawn)
9418                && (board[toY][toX] == EmptySquare)) {
9419         board[fromY][fromX] = EmptySquare;
9420         board[toY][toX] = WhitePawn;
9421         captured = board[toY - 1][toX];
9422         board[toY - 1][toX] = EmptySquare;
9423     } else if ((fromY == BOARD_HEIGHT-4)
9424                && (toX == fromX)
9425                && gameInfo.variant == VariantBerolina
9426                && (board[fromY][fromX] == WhitePawn)
9427                && (board[toY][toX] == EmptySquare)) {
9428         board[fromY][fromX] = EmptySquare;
9429         board[toY][toX] = WhitePawn;
9430         if(oldEP & EP_BEROLIN_A) {
9431                 captured = board[fromY][fromX-1];
9432                 board[fromY][fromX-1] = EmptySquare;
9433         }else{  captured = board[fromY][fromX+1];
9434                 board[fromY][fromX+1] = EmptySquare;
9435         }
9436     } else if (board[fromY][fromX] == king
9437         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9438                && toY == fromY && toX > fromX+1) {
9439         board[fromY][fromX] = EmptySquare;
9440         board[toY][toX] = king;
9441         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9442         board[fromY][BOARD_RGHT-1] = EmptySquare;
9443     } else if (board[fromY][fromX] == king
9444         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9445                && toY == fromY && toX < fromX-1) {
9446         board[fromY][fromX] = EmptySquare;
9447         board[toY][toX] = king;
9448         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9449         board[fromY][BOARD_LEFT] = EmptySquare;
9450     } else if (fromY == 7 && fromX == 3
9451                && board[fromY][fromX] == BlackKing
9452                && toY == 7 && toX == 5) {
9453         board[fromY][fromX] = EmptySquare;
9454         board[toY][toX] = BlackKing;
9455         board[fromY][7] = EmptySquare;
9456         board[toY][4] = BlackRook;
9457     } else if (fromY == 7 && fromX == 3
9458                && board[fromY][fromX] == BlackKing
9459                && toY == 7 && toX == 1) {
9460         board[fromY][fromX] = EmptySquare;
9461         board[toY][toX] = BlackKing;
9462         board[fromY][0] = EmptySquare;
9463         board[toY][2] = BlackRook;
9464     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9465                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9466                && toY < promoRank && promoChar
9467                ) {
9468         /* black pawn promotion */
9469         board[toY][toX] = CharToPiece(ToLower(promoChar));
9470         if(gameInfo.variant==VariantBughouse ||
9471            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9472             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9473         board[fromY][fromX] = EmptySquare;
9474     } else if ((fromY < BOARD_HEIGHT>>1)
9475                && (toX != fromX)
9476                && gameInfo.variant != VariantXiangqi
9477                && gameInfo.variant != VariantBerolina
9478                && (board[fromY][fromX] == BlackPawn)
9479                && (board[toY][toX] == EmptySquare)) {
9480         board[fromY][fromX] = EmptySquare;
9481         board[toY][toX] = BlackPawn;
9482         captured = board[toY + 1][toX];
9483         board[toY + 1][toX] = EmptySquare;
9484     } else if ((fromY == 3)
9485                && (toX == fromX)
9486                && gameInfo.variant == VariantBerolina
9487                && (board[fromY][fromX] == BlackPawn)
9488                && (board[toY][toX] == EmptySquare)) {
9489         board[fromY][fromX] = EmptySquare;
9490         board[toY][toX] = BlackPawn;
9491         if(oldEP & EP_BEROLIN_A) {
9492                 captured = board[fromY][fromX-1];
9493                 board[fromY][fromX-1] = EmptySquare;
9494         }else{  captured = board[fromY][fromX+1];
9495                 board[fromY][fromX+1] = EmptySquare;
9496         }
9497     } else {
9498         board[toY][toX] = board[fromY][fromX];
9499         board[fromY][fromX] = EmptySquare;
9500     }
9501   }
9502
9503     if (gameInfo.holdingsWidth != 0) {
9504
9505       /* !!A lot more code needs to be written to support holdings  */
9506       /* [HGM] OK, so I have written it. Holdings are stored in the */
9507       /* penultimate board files, so they are automaticlly stored   */
9508       /* in the game history.                                       */
9509       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9510                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9511         /* Delete from holdings, by decreasing count */
9512         /* and erasing image if necessary            */
9513         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9514         if(p < (int) BlackPawn) { /* white drop */
9515              p -= (int)WhitePawn;
9516                  p = PieceToNumber((ChessSquare)p);
9517              if(p >= gameInfo.holdingsSize) p = 0;
9518              if(--board[p][BOARD_WIDTH-2] <= 0)
9519                   board[p][BOARD_WIDTH-1] = EmptySquare;
9520              if((int)board[p][BOARD_WIDTH-2] < 0)
9521                         board[p][BOARD_WIDTH-2] = 0;
9522         } else {                  /* black drop */
9523              p -= (int)BlackPawn;
9524                  p = PieceToNumber((ChessSquare)p);
9525              if(p >= gameInfo.holdingsSize) p = 0;
9526              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9527                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9528              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9529                         board[BOARD_HEIGHT-1-p][1] = 0;
9530         }
9531       }
9532       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9533           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9534         /* [HGM] holdings: Add to holdings, if holdings exist */
9535         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9536                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9537                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9538         }
9539         p = (int) captured;
9540         if (p >= (int) BlackPawn) {
9541           p -= (int)BlackPawn;
9542           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9543                   /* in Shogi restore piece to its original  first */
9544                   captured = (ChessSquare) (DEMOTED captured);
9545                   p = DEMOTED p;
9546           }
9547           p = PieceToNumber((ChessSquare)p);
9548           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9549           board[p][BOARD_WIDTH-2]++;
9550           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9551         } else {
9552           p -= (int)WhitePawn;
9553           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9554                   captured = (ChessSquare) (DEMOTED captured);
9555                   p = DEMOTED p;
9556           }
9557           p = PieceToNumber((ChessSquare)p);
9558           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9559           board[BOARD_HEIGHT-1-p][1]++;
9560           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9561         }
9562       }
9563     } else if (gameInfo.variant == VariantAtomic) {
9564       if (captured != EmptySquare) {
9565         int y, x;
9566         for (y = toY-1; y <= toY+1; y++) {
9567           for (x = toX-1; x <= toX+1; x++) {
9568             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9569                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9570               board[y][x] = EmptySquare;
9571             }
9572           }
9573         }
9574         board[toY][toX] = EmptySquare;
9575       }
9576     }
9577     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9578         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9579     } else
9580     if(promoChar == '+') {
9581         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9582         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9583     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9584         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9585         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9586            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9587         board[toY][toX] = newPiece;
9588     }
9589     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9590                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9591         // [HGM] superchess: take promotion piece out of holdings
9592         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9593         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9594             if(!--board[k][BOARD_WIDTH-2])
9595                 board[k][BOARD_WIDTH-1] = EmptySquare;
9596         } else {
9597             if(!--board[BOARD_HEIGHT-1-k][1])
9598                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9599         }
9600     }
9601
9602 }
9603
9604 /* Updates forwardMostMove */
9605 void
9606 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9607 {
9608 //    forwardMostMove++; // [HGM] bare: moved downstream
9609
9610     (void) CoordsToAlgebraic(boards[forwardMostMove],
9611                              PosFlags(forwardMostMove),
9612                              fromY, fromX, toY, toX, promoChar,
9613                              parseList[forwardMostMove]);
9614
9615     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9616         int timeLeft; static int lastLoadFlag=0; int king, piece;
9617         piece = boards[forwardMostMove][fromY][fromX];
9618         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9619         if(gameInfo.variant == VariantKnightmate)
9620             king += (int) WhiteUnicorn - (int) WhiteKing;
9621         if(forwardMostMove == 0) {
9622             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9623                 fprintf(serverMoves, "%s;", UserName());
9624             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9625                 fprintf(serverMoves, "%s;", second.tidy);
9626             fprintf(serverMoves, "%s;", first.tidy);
9627             if(gameMode == MachinePlaysWhite)
9628                 fprintf(serverMoves, "%s;", UserName());
9629             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9630                 fprintf(serverMoves, "%s;", second.tidy);
9631         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9632         lastLoadFlag = loadFlag;
9633         // print base move
9634         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9635         // print castling suffix
9636         if( toY == fromY && piece == king ) {
9637             if(toX-fromX > 1)
9638                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9639             if(fromX-toX >1)
9640                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9641         }
9642         // e.p. suffix
9643         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9644              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9645              boards[forwardMostMove][toY][toX] == EmptySquare
9646              && fromX != toX && fromY != toY)
9647                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9648         // promotion suffix
9649         if(promoChar != NULLCHAR) {
9650             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9651                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9652                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9653             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9654         }
9655         if(!loadFlag) {
9656                 char buf[MOVE_LEN*2], *p; int len;
9657             fprintf(serverMoves, "/%d/%d",
9658                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9659             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9660             else                      timeLeft = blackTimeRemaining/1000;
9661             fprintf(serverMoves, "/%d", timeLeft);
9662                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9663                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9664                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9665                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9666             fprintf(serverMoves, "/%s", buf);
9667         }
9668         fflush(serverMoves);
9669     }
9670
9671     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9672         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9673       return;
9674     }
9675     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9676     if (commentList[forwardMostMove+1] != NULL) {
9677         free(commentList[forwardMostMove+1]);
9678         commentList[forwardMostMove+1] = NULL;
9679     }
9680     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9681     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9682     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9683     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9684     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9685     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9686     adjustedClock = FALSE;
9687     gameInfo.result = GameUnfinished;
9688     if (gameInfo.resultDetails != NULL) {
9689         free(gameInfo.resultDetails);
9690         gameInfo.resultDetails = NULL;
9691     }
9692     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9693                               moveList[forwardMostMove - 1]);
9694     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9695       case MT_NONE:
9696       case MT_STALEMATE:
9697       default:
9698         break;
9699       case MT_CHECK:
9700         if(gameInfo.variant != VariantShogi)
9701             strcat(parseList[forwardMostMove - 1], "+");
9702         break;
9703       case MT_CHECKMATE:
9704       case MT_STAINMATE:
9705         strcat(parseList[forwardMostMove - 1], "#");
9706         break;
9707     }
9708
9709 }
9710
9711 /* Updates currentMove if not pausing */
9712 void
9713 ShowMove (int fromX, int fromY, int toX, int toY)
9714 {
9715     int instant = (gameMode == PlayFromGameFile) ?
9716         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9717     if(appData.noGUI) return;
9718     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9719         if (!instant) {
9720             if (forwardMostMove == currentMove + 1) {
9721                 AnimateMove(boards[forwardMostMove - 1],
9722                             fromX, fromY, toX, toY);
9723             }
9724             if (appData.highlightLastMove) {
9725                 SetHighlights(fromX, fromY, toX, toY);
9726             }
9727         }
9728         currentMove = forwardMostMove;
9729     }
9730
9731     if (instant) return;
9732
9733     DisplayMove(currentMove - 1);
9734     DrawPosition(FALSE, boards[currentMove]);
9735     DisplayBothClocks();
9736     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9737 }
9738
9739 void
9740 SendEgtPath (ChessProgramState *cps)
9741 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9742         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9743
9744         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9745
9746         while(*p) {
9747             char c, *q = name+1, *r, *s;
9748
9749             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9750             while(*p && *p != ',') *q++ = *p++;
9751             *q++ = ':'; *q = 0;
9752             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9753                 strcmp(name, ",nalimov:") == 0 ) {
9754                 // take nalimov path from the menu-changeable option first, if it is defined
9755               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9756                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9757             } else
9758             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9759                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9760                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9761                 s = r = StrStr(s, ":") + 1; // beginning of path info
9762                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9763                 c = *r; *r = 0;             // temporarily null-terminate path info
9764                     *--q = 0;               // strip of trailig ':' from name
9765                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9766                 *r = c;
9767                 SendToProgram(buf,cps);     // send egtbpath command for this format
9768             }
9769             if(*p == ',') p++; // read away comma to position for next format name
9770         }
9771 }
9772
9773 void
9774 InitChessProgram (ChessProgramState *cps, int setup)
9775 /* setup needed to setup FRC opening position */
9776 {
9777     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9778     if (appData.noChessProgram) return;
9779     hintRequested = FALSE;
9780     bookRequested = FALSE;
9781
9782     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9783     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9784     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9785     if(cps->memSize) { /* [HGM] memory */
9786       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9787         SendToProgram(buf, cps);
9788     }
9789     SendEgtPath(cps); /* [HGM] EGT */
9790     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9791       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9792         SendToProgram(buf, cps);
9793     }
9794
9795     SendToProgram(cps->initString, cps);
9796     if (gameInfo.variant != VariantNormal &&
9797         gameInfo.variant != VariantLoadable
9798         /* [HGM] also send variant if board size non-standard */
9799         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9800                                             ) {
9801       char *v = VariantName(gameInfo.variant);
9802       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9803         /* [HGM] in protocol 1 we have to assume all variants valid */
9804         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9805         DisplayFatalError(buf, 0, 1);
9806         return;
9807       }
9808
9809       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9810       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9811       if( gameInfo.variant == VariantXiangqi )
9812            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9813       if( gameInfo.variant == VariantShogi )
9814            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9815       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9816            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9817       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9818           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9819            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9820       if( gameInfo.variant == VariantCourier )
9821            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9822       if( gameInfo.variant == VariantSuper )
9823            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9824       if( gameInfo.variant == VariantGreat )
9825            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9826       if( gameInfo.variant == VariantSChess )
9827            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9828       if( gameInfo.variant == VariantGrand )
9829            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9830
9831       if(overruled) {
9832         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9833                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9834            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9835            if(StrStr(cps->variants, b) == NULL) {
9836                // specific sized variant not known, check if general sizing allowed
9837                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9838                    if(StrStr(cps->variants, "boardsize") == NULL) {
9839                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9840                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9841                        DisplayFatalError(buf, 0, 1);
9842                        return;
9843                    }
9844                    /* [HGM] here we really should compare with the maximum supported board size */
9845                }
9846            }
9847       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9848       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9849       SendToProgram(buf, cps);
9850     }
9851     currentlyInitializedVariant = gameInfo.variant;
9852
9853     /* [HGM] send opening position in FRC to first engine */
9854     if(setup) {
9855           SendToProgram("force\n", cps);
9856           SendBoard(cps, 0);
9857           /* engine is now in force mode! Set flag to wake it up after first move. */
9858           setboardSpoiledMachineBlack = 1;
9859     }
9860
9861     if (cps->sendICS) {
9862       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9863       SendToProgram(buf, cps);
9864     }
9865     cps->maybeThinking = FALSE;
9866     cps->offeredDraw = 0;
9867     if (!appData.icsActive) {
9868         SendTimeControl(cps, movesPerSession, timeControl,
9869                         timeIncrement, appData.searchDepth,
9870                         searchTime);
9871     }
9872     if (appData.showThinking
9873         // [HGM] thinking: four options require thinking output to be sent
9874         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9875                                 ) {
9876         SendToProgram("post\n", cps);
9877     }
9878     SendToProgram("hard\n", cps);
9879     if (!appData.ponderNextMove) {
9880         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9881            it without being sure what state we are in first.  "hard"
9882            is not a toggle, so that one is OK.
9883          */
9884         SendToProgram("easy\n", cps);
9885     }
9886     if (cps->usePing) {
9887       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9888       SendToProgram(buf, cps);
9889     }
9890     cps->initDone = TRUE;
9891     ClearEngineOutputPane(cps == &second);
9892 }
9893
9894
9895 void
9896 StartChessProgram (ChessProgramState *cps)
9897 {
9898     char buf[MSG_SIZ];
9899     int err;
9900
9901     if (appData.noChessProgram) return;
9902     cps->initDone = FALSE;
9903
9904     if (strcmp(cps->host, "localhost") == 0) {
9905         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9906     } else if (*appData.remoteShell == NULLCHAR) {
9907         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9908     } else {
9909         if (*appData.remoteUser == NULLCHAR) {
9910           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9911                     cps->program);
9912         } else {
9913           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9914                     cps->host, appData.remoteUser, cps->program);
9915         }
9916         err = StartChildProcess(buf, "", &cps->pr);
9917     }
9918
9919     if (err != 0) {
9920       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9921         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9922         if(cps != &first) return;
9923         appData.noChessProgram = TRUE;
9924         ThawUI();
9925         SetNCPMode();
9926 //      DisplayFatalError(buf, err, 1);
9927 //      cps->pr = NoProc;
9928 //      cps->isr = NULL;
9929         return;
9930     }
9931
9932     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9933     if (cps->protocolVersion > 1) {
9934       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9935       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9936       cps->comboCnt = 0;  //                and values of combo boxes
9937       SendToProgram(buf, cps);
9938     } else {
9939       SendToProgram("xboard\n", cps);
9940     }
9941 }
9942
9943 void
9944 TwoMachinesEventIfReady P((void))
9945 {
9946   static int curMess = 0;
9947   if (first.lastPing != first.lastPong) {
9948     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9949     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9950     return;
9951   }
9952   if (second.lastPing != second.lastPong) {
9953     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9954     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9955     return;
9956   }
9957   DisplayMessage("", ""); curMess = 0;
9958   ThawUI();
9959   TwoMachinesEvent();
9960 }
9961
9962 char *
9963 MakeName (char *template)
9964 {
9965     time_t clock;
9966     struct tm *tm;
9967     static char buf[MSG_SIZ];
9968     char *p = buf;
9969     int i;
9970
9971     clock = time((time_t *)NULL);
9972     tm = localtime(&clock);
9973
9974     while(*p++ = *template++) if(p[-1] == '%') {
9975         switch(*template++) {
9976           case 0:   *p = 0; return buf;
9977           case 'Y': i = tm->tm_year+1900; break;
9978           case 'y': i = tm->tm_year-100; break;
9979           case 'M': i = tm->tm_mon+1; break;
9980           case 'd': i = tm->tm_mday; break;
9981           case 'h': i = tm->tm_hour; break;
9982           case 'm': i = tm->tm_min; break;
9983           case 's': i = tm->tm_sec; break;
9984           default:  i = 0;
9985         }
9986         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9987     }
9988     return buf;
9989 }
9990
9991 int
9992 CountPlayers (char *p)
9993 {
9994     int n = 0;
9995     while(p = strchr(p, '\n')) p++, n++; // count participants
9996     return n;
9997 }
9998
9999 FILE *
10000 WriteTourneyFile (char *results, FILE *f)
10001 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10002     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10003     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10004         // create a file with tournament description
10005         fprintf(f, "-participants {%s}\n", appData.participants);
10006         fprintf(f, "-seedBase %d\n", appData.seedBase);
10007         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10008         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10009         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10010         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10011         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10012         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10013         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10014         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10015         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10016         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10017         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10018         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10019         if(searchTime > 0)
10020                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10021         else {
10022                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10023                 fprintf(f, "-tc %s\n", appData.timeControl);
10024                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10025         }
10026         fprintf(f, "-results \"%s\"\n", results);
10027     }
10028     return f;
10029 }
10030
10031 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10032
10033 void
10034 Substitute (char *participants, int expunge)
10035 {
10036     int i, changed, changes=0, nPlayers=0;
10037     char *p, *q, *r, buf[MSG_SIZ];
10038     if(participants == NULL) return;
10039     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10040     r = p = participants; q = appData.participants;
10041     while(*p && *p == *q) {
10042         if(*p == '\n') r = p+1, nPlayers++;
10043         p++; q++;
10044     }
10045     if(*p) { // difference
10046         while(*p && *p++ != '\n');
10047         while(*q && *q++ != '\n');
10048       changed = nPlayers;
10049         changes = 1 + (strcmp(p, q) != 0);
10050     }
10051     if(changes == 1) { // a single engine mnemonic was changed
10052         q = r; while(*q) nPlayers += (*q++ == '\n');
10053         p = buf; while(*r && (*p = *r++) != '\n') p++;
10054         *p = NULLCHAR;
10055         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10056         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10057         if(mnemonic[i]) { // The substitute is valid
10058             FILE *f;
10059             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10060                 flock(fileno(f), LOCK_EX);
10061                 ParseArgsFromFile(f);
10062                 fseek(f, 0, SEEK_SET);
10063                 FREE(appData.participants); appData.participants = participants;
10064                 if(expunge) { // erase results of replaced engine
10065                     int len = strlen(appData.results), w, b, dummy;
10066                     for(i=0; i<len; i++) {
10067                         Pairing(i, nPlayers, &w, &b, &dummy);
10068                         if((w == changed || b == changed) && appData.results[i] == '*') {
10069                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10070                             fclose(f);
10071                             return;
10072                         }
10073                     }
10074                     for(i=0; i<len; i++) {
10075                         Pairing(i, nPlayers, &w, &b, &dummy);
10076                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10077                     }
10078                 }
10079                 WriteTourneyFile(appData.results, f);
10080                 fclose(f); // release lock
10081                 return;
10082             }
10083         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10084     }
10085     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10086     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10087     free(participants);
10088     return;
10089 }
10090
10091 int
10092 CheckPlayers (char *participants)
10093 {
10094         int i;
10095         char buf[MSG_SIZ], *p;
10096         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10097         while(p = strchr(participants, '\n')) {
10098             *p = NULLCHAR;
10099             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10100             if(!mnemonic[i]) {
10101                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10102                 *p = '\n';
10103                 DisplayError(buf, 0);
10104                 return 1;
10105             }
10106             *p = '\n';
10107             participants = p + 1;
10108         }
10109         return 0;
10110 }
10111
10112 int
10113 CreateTourney (char *name)
10114 {
10115         FILE *f;
10116         if(matchMode && strcmp(name, appData.tourneyFile)) {
10117              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10118         }
10119         if(name[0] == NULLCHAR) {
10120             if(appData.participants[0])
10121                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10122             return 0;
10123         }
10124         f = fopen(name, "r");
10125         if(f) { // file exists
10126             ASSIGN(appData.tourneyFile, name);
10127             ParseArgsFromFile(f); // parse it
10128         } else {
10129             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10130             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10131                 DisplayError(_("Not enough participants"), 0);
10132                 return 0;
10133             }
10134             if(CheckPlayers(appData.participants)) return 0;
10135             ASSIGN(appData.tourneyFile, name);
10136             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10137             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10138         }
10139         fclose(f);
10140         appData.noChessProgram = FALSE;
10141         appData.clockMode = TRUE;
10142         SetGNUMode();
10143         return 1;
10144 }
10145
10146 int
10147 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10148 {
10149     char buf[MSG_SIZ], *p, *q;
10150     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10151     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10152     skip = !all && group[0]; // if group requested, we start in skip mode
10153     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10154         p = names; q = buf; header = 0;
10155         while(*p && *p != '\n') *q++ = *p++;
10156         *q = 0;
10157         if(*p == '\n') p++;
10158         if(buf[0] == '#') {
10159             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10160             depth++; // we must be entering a new group
10161             if(all) continue; // suppress printing group headers when complete list requested
10162             header = 1;
10163             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10164         }
10165         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10166         if(engineList[i]) free(engineList[i]);
10167         engineList[i] = strdup(buf);
10168         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10169         if(engineMnemonic[i]) free(engineMnemonic[i]);
10170         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10171             strcat(buf, " (");
10172             sscanf(q + 8, "%s", buf + strlen(buf));
10173             strcat(buf, ")");
10174         }
10175         engineMnemonic[i] = strdup(buf);
10176         i++;
10177     }
10178     engineList[i] = engineMnemonic[i] = NULL;
10179     return i;
10180 }
10181
10182 // following implemented as macro to avoid type limitations
10183 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10184
10185 void
10186 SwapEngines (int n)
10187 {   // swap settings for first engine and other engine (so far only some selected options)
10188     int h;
10189     char *p;
10190     if(n == 0) return;
10191     SWAP(directory, p)
10192     SWAP(chessProgram, p)
10193     SWAP(isUCI, h)
10194     SWAP(hasOwnBookUCI, h)
10195     SWAP(protocolVersion, h)
10196     SWAP(reuse, h)
10197     SWAP(scoreIsAbsolute, h)
10198     SWAP(timeOdds, h)
10199     SWAP(logo, p)
10200     SWAP(pgnName, p)
10201     SWAP(pvSAN, h)
10202     SWAP(engOptions, p)
10203     SWAP(engInitString, p)
10204     SWAP(computerString, p)
10205     SWAP(features, p)
10206     SWAP(fenOverride, p)
10207     SWAP(NPS, h)
10208     SWAP(accumulateTC, h)
10209     SWAP(host, p)
10210 }
10211
10212 int
10213 GetEngineLine (char *s, int n)
10214 {
10215     int i;
10216     char buf[MSG_SIZ];
10217     extern char *icsNames;
10218     if(!s || !*s) return 0;
10219     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10220     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10221     if(!mnemonic[i]) return 0;
10222     if(n == 11) return 1; // just testing if there was a match
10223     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10224     if(n == 1) SwapEngines(n);
10225     ParseArgsFromString(buf);
10226     if(n == 1) SwapEngines(n);
10227     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10228         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10229         ParseArgsFromString(buf);
10230     }
10231     return 1;
10232 }
10233
10234 int
10235 SetPlayer (int player, char *p)
10236 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10237     int i;
10238     char buf[MSG_SIZ], *engineName;
10239     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10240     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10241     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10242     if(mnemonic[i]) {
10243         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10244         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10245         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10246         ParseArgsFromString(buf);
10247     }
10248     free(engineName);
10249     return i;
10250 }
10251
10252 char *recentEngines;
10253
10254 void
10255 RecentEngineEvent (int nr)
10256 {
10257     int n;
10258 //    SwapEngines(1); // bump first to second
10259 //    ReplaceEngine(&second, 1); // and load it there
10260     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10261     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10262     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10263         ReplaceEngine(&first, 0);
10264         FloatToFront(&appData.recentEngineList, command[n]);
10265     }
10266 }
10267
10268 int
10269 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10270 {   // determine players from game number
10271     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10272
10273     if(appData.tourneyType == 0) {
10274         roundsPerCycle = (nPlayers - 1) | 1;
10275         pairingsPerRound = nPlayers / 2;
10276     } else if(appData.tourneyType > 0) {
10277         roundsPerCycle = nPlayers - appData.tourneyType;
10278         pairingsPerRound = appData.tourneyType;
10279     }
10280     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10281     gamesPerCycle = gamesPerRound * roundsPerCycle;
10282     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10283     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10284     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10285     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10286     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10287     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10288
10289     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10290     if(appData.roundSync) *syncInterval = gamesPerRound;
10291
10292     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10293
10294     if(appData.tourneyType == 0) {
10295         if(curPairing == (nPlayers-1)/2 ) {
10296             *whitePlayer = curRound;
10297             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10298         } else {
10299             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10300             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10301             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10302             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10303         }
10304     } else if(appData.tourneyType > 1) {
10305         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10306         *whitePlayer = curRound + appData.tourneyType;
10307     } else if(appData.tourneyType > 0) {
10308         *whitePlayer = curPairing;
10309         *blackPlayer = curRound + appData.tourneyType;
10310     }
10311
10312     // take care of white/black alternation per round. 
10313     // For cycles and games this is already taken care of by default, derived from matchGame!
10314     return curRound & 1;
10315 }
10316
10317 int
10318 NextTourneyGame (int nr, int *swapColors)
10319 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10320     char *p, *q;
10321     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10322     FILE *tf;
10323     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10324     tf = fopen(appData.tourneyFile, "r");
10325     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10326     ParseArgsFromFile(tf); fclose(tf);
10327     InitTimeControls(); // TC might be altered from tourney file
10328
10329     nPlayers = CountPlayers(appData.participants); // count participants
10330     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10331     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10332
10333     if(syncInterval) {
10334         p = q = appData.results;
10335         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10336         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10337             DisplayMessage(_("Waiting for other game(s)"),"");
10338             waitingForGame = TRUE;
10339             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10340             return 0;
10341         }
10342         waitingForGame = FALSE;
10343     }
10344
10345     if(appData.tourneyType < 0) {
10346         if(nr>=0 && !pairingReceived) {
10347             char buf[1<<16];
10348             if(pairing.pr == NoProc) {
10349                 if(!appData.pairingEngine[0]) {
10350                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10351                     return 0;
10352                 }
10353                 StartChessProgram(&pairing); // starts the pairing engine
10354             }
10355             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10356             SendToProgram(buf, &pairing);
10357             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10358             SendToProgram(buf, &pairing);
10359             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10360         }
10361         pairingReceived = 0;                              // ... so we continue here 
10362         *swapColors = 0;
10363         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10364         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10365         matchGame = 1; roundNr = nr / syncInterval + 1;
10366     }
10367
10368     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10369
10370     // redefine engines, engine dir, etc.
10371     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10372     if(first.pr == NoProc) {
10373       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10374       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10375     }
10376     if(second.pr == NoProc) {
10377       SwapEngines(1);
10378       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10379       SwapEngines(1);         // and make that valid for second engine by swapping
10380       InitEngine(&second, 1);
10381     }
10382     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10383     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10384     return 1;
10385 }
10386
10387 void
10388 NextMatchGame ()
10389 {   // performs game initialization that does not invoke engines, and then tries to start the game
10390     int res, firstWhite, swapColors = 0;
10391     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10392     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
10393         char buf[MSG_SIZ];
10394         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10395         if(strcmp(buf, currentDebugFile)) { // name has changed
10396             FILE *f = fopen(buf, "w");
10397             if(f) { // if opening the new file failed, just keep using the old one
10398                 ASSIGN(currentDebugFile, buf);
10399                 fclose(debugFP);
10400                 debugFP = f;
10401             }
10402             if(appData.serverFileName) {
10403                 if(serverFP) fclose(serverFP);
10404                 serverFP = fopen(appData.serverFileName, "w");
10405                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10406                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10407             }
10408         }
10409     }
10410     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10411     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10412     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10413     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10414     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10415     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10416     Reset(FALSE, first.pr != NoProc);
10417     res = LoadGameOrPosition(matchGame); // setup game
10418     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10419     if(!res) return; // abort when bad game/pos file
10420     TwoMachinesEvent();
10421 }
10422
10423 void
10424 UserAdjudicationEvent (int result)
10425 {
10426     ChessMove gameResult = GameIsDrawn;
10427
10428     if( result > 0 ) {
10429         gameResult = WhiteWins;
10430     }
10431     else if( result < 0 ) {
10432         gameResult = BlackWins;
10433     }
10434
10435     if( gameMode == TwoMachinesPlay ) {
10436         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10437     }
10438 }
10439
10440
10441 // [HGM] save: calculate checksum of game to make games easily identifiable
10442 int
10443 StringCheckSum (char *s)
10444 {
10445         int i = 0;
10446         if(s==NULL) return 0;
10447         while(*s) i = i*259 + *s++;
10448         return i;
10449 }
10450
10451 int
10452 GameCheckSum ()
10453 {
10454         int i, sum=0;
10455         for(i=backwardMostMove; i<forwardMostMove; i++) {
10456                 sum += pvInfoList[i].depth;
10457                 sum += StringCheckSum(parseList[i]);
10458                 sum += StringCheckSum(commentList[i]);
10459                 sum *= 261;
10460         }
10461         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10462         return sum + StringCheckSum(commentList[i]);
10463 } // end of save patch
10464
10465 void
10466 GameEnds (ChessMove result, char *resultDetails, int whosays)
10467 {
10468     GameMode nextGameMode;
10469     int isIcsGame;
10470     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10471
10472     if(endingGame) return; /* [HGM] crash: forbid recursion */
10473     endingGame = 1;
10474     if(twoBoards) { // [HGM] dual: switch back to one board
10475         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10476         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10477     }
10478     if (appData.debugMode) {
10479       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10480               result, resultDetails ? resultDetails : "(null)", whosays);
10481     }
10482
10483     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10484
10485     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10486         /* If we are playing on ICS, the server decides when the
10487            game is over, but the engine can offer to draw, claim
10488            a draw, or resign.
10489          */
10490 #if ZIPPY
10491         if (appData.zippyPlay && first.initDone) {
10492             if (result == GameIsDrawn) {
10493                 /* In case draw still needs to be claimed */
10494                 SendToICS(ics_prefix);
10495                 SendToICS("draw\n");
10496             } else if (StrCaseStr(resultDetails, "resign")) {
10497                 SendToICS(ics_prefix);
10498                 SendToICS("resign\n");
10499             }
10500         }
10501 #endif
10502         endingGame = 0; /* [HGM] crash */
10503         return;
10504     }
10505
10506     /* If we're loading the game from a file, stop */
10507     if (whosays == GE_FILE) {
10508       (void) StopLoadGameTimer();
10509       gameFileFP = NULL;
10510     }
10511
10512     /* Cancel draw offers */
10513     first.offeredDraw = second.offeredDraw = 0;
10514
10515     /* If this is an ICS game, only ICS can really say it's done;
10516        if not, anyone can. */
10517     isIcsGame = (gameMode == IcsPlayingWhite ||
10518                  gameMode == IcsPlayingBlack ||
10519                  gameMode == IcsObserving    ||
10520                  gameMode == IcsExamining);
10521
10522     if (!isIcsGame || whosays == GE_ICS) {
10523         /* OK -- not an ICS game, or ICS said it was done */
10524         StopClocks();
10525         if (!isIcsGame && !appData.noChessProgram)
10526           SetUserThinkingEnables();
10527
10528         /* [HGM] if a machine claims the game end we verify this claim */
10529         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10530             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10531                 char claimer;
10532                 ChessMove trueResult = (ChessMove) -1;
10533
10534                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10535                                             first.twoMachinesColor[0] :
10536                                             second.twoMachinesColor[0] ;
10537
10538                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10539                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10540                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10541                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10542                 } else
10543                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10544                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10545                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10546                 } else
10547                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10548                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10549                 }
10550
10551                 // now verify win claims, but not in drop games, as we don't understand those yet
10552                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10553                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10554                     (result == WhiteWins && claimer == 'w' ||
10555                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10556                       if (appData.debugMode) {
10557                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10558                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10559                       }
10560                       if(result != trueResult) {
10561                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10562                               result = claimer == 'w' ? BlackWins : WhiteWins;
10563                               resultDetails = buf;
10564                       }
10565                 } else
10566                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10567                     && (forwardMostMove <= backwardMostMove ||
10568                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10569                         (claimer=='b')==(forwardMostMove&1))
10570                                                                                   ) {
10571                       /* [HGM] verify: draws that were not flagged are false claims */
10572                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10573                       result = claimer == 'w' ? BlackWins : WhiteWins;
10574                       resultDetails = buf;
10575                 }
10576                 /* (Claiming a loss is accepted no questions asked!) */
10577             }
10578             /* [HGM] bare: don't allow bare King to win */
10579             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10580                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10581                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10582                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10583                && result != GameIsDrawn)
10584             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10585                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10586                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10587                         if(p >= 0 && p <= (int)WhiteKing) k++;
10588                 }
10589                 if (appData.debugMode) {
10590                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10591                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10592                 }
10593                 if(k <= 1) {
10594                         result = GameIsDrawn;
10595                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10596                         resultDetails = buf;
10597                 }
10598             }
10599         }
10600
10601
10602         if(serverMoves != NULL && !loadFlag) { char c = '=';
10603             if(result==WhiteWins) c = '+';
10604             if(result==BlackWins) c = '-';
10605             if(resultDetails != NULL)
10606                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10607         }
10608         if (resultDetails != NULL) {
10609             gameInfo.result = result;
10610             gameInfo.resultDetails = StrSave(resultDetails);
10611
10612             /* display last move only if game was not loaded from file */
10613             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10614                 DisplayMove(currentMove - 1);
10615
10616             if (forwardMostMove != 0) {
10617                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10618                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10619                                                                 ) {
10620                     if (*appData.saveGameFile != NULLCHAR) {
10621                         SaveGameToFile(appData.saveGameFile, TRUE);
10622                     } else if (appData.autoSaveGames) {
10623                         AutoSaveGame();
10624                     }
10625                     if (*appData.savePositionFile != NULLCHAR) {
10626                         SavePositionToFile(appData.savePositionFile);
10627                     }
10628                 }
10629             }
10630
10631             /* Tell program how game ended in case it is learning */
10632             /* [HGM] Moved this to after saving the PGN, just in case */
10633             /* engine died and we got here through time loss. In that */
10634             /* case we will get a fatal error writing the pipe, which */
10635             /* would otherwise lose us the PGN.                       */
10636             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10637             /* output during GameEnds should never be fatal anymore   */
10638             if (gameMode == MachinePlaysWhite ||
10639                 gameMode == MachinePlaysBlack ||
10640                 gameMode == TwoMachinesPlay ||
10641                 gameMode == IcsPlayingWhite ||
10642                 gameMode == IcsPlayingBlack ||
10643                 gameMode == BeginningOfGame) {
10644                 char buf[MSG_SIZ];
10645                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10646                         resultDetails);
10647                 if (first.pr != NoProc) {
10648                     SendToProgram(buf, &first);
10649                 }
10650                 if (second.pr != NoProc &&
10651                     gameMode == TwoMachinesPlay) {
10652                     SendToProgram(buf, &second);
10653                 }
10654             }
10655         }
10656
10657         if (appData.icsActive) {
10658             if (appData.quietPlay &&
10659                 (gameMode == IcsPlayingWhite ||
10660                  gameMode == IcsPlayingBlack)) {
10661                 SendToICS(ics_prefix);
10662                 SendToICS("set shout 1\n");
10663             }
10664             nextGameMode = IcsIdle;
10665             ics_user_moved = FALSE;
10666             /* clean up premove.  It's ugly when the game has ended and the
10667              * premove highlights are still on the board.
10668              */
10669             if (gotPremove) {
10670               gotPremove = FALSE;
10671               ClearPremoveHighlights();
10672               DrawPosition(FALSE, boards[currentMove]);
10673             }
10674             if (whosays == GE_ICS) {
10675                 switch (result) {
10676                 case WhiteWins:
10677                     if (gameMode == IcsPlayingWhite)
10678                         PlayIcsWinSound();
10679                     else if(gameMode == IcsPlayingBlack)
10680                         PlayIcsLossSound();
10681                     break;
10682                 case BlackWins:
10683                     if (gameMode == IcsPlayingBlack)
10684                         PlayIcsWinSound();
10685                     else if(gameMode == IcsPlayingWhite)
10686                         PlayIcsLossSound();
10687                     break;
10688                 case GameIsDrawn:
10689                     PlayIcsDrawSound();
10690                     break;
10691                 default:
10692                     PlayIcsUnfinishedSound();
10693                 }
10694             }
10695         } else if (gameMode == EditGame ||
10696                    gameMode == PlayFromGameFile ||
10697                    gameMode == AnalyzeMode ||
10698                    gameMode == AnalyzeFile) {
10699             nextGameMode = gameMode;
10700         } else {
10701             nextGameMode = EndOfGame;
10702         }
10703         pausing = FALSE;
10704         ModeHighlight();
10705     } else {
10706         nextGameMode = gameMode;
10707     }
10708
10709     if (appData.noChessProgram) {
10710         gameMode = nextGameMode;
10711         ModeHighlight();
10712         endingGame = 0; /* [HGM] crash */
10713         return;
10714     }
10715
10716     if (first.reuse) {
10717         /* Put first chess program into idle state */
10718         if (first.pr != NoProc &&
10719             (gameMode == MachinePlaysWhite ||
10720              gameMode == MachinePlaysBlack ||
10721              gameMode == TwoMachinesPlay ||
10722              gameMode == IcsPlayingWhite ||
10723              gameMode == IcsPlayingBlack ||
10724              gameMode == BeginningOfGame)) {
10725             SendToProgram("force\n", &first);
10726             if (first.usePing) {
10727               char buf[MSG_SIZ];
10728               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10729               SendToProgram(buf, &first);
10730             }
10731         }
10732     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10733         /* Kill off first chess program */
10734         if (first.isr != NULL)
10735           RemoveInputSource(first.isr);
10736         first.isr = NULL;
10737
10738         if (first.pr != NoProc) {
10739             ExitAnalyzeMode();
10740             DoSleep( appData.delayBeforeQuit );
10741             SendToProgram("quit\n", &first);
10742             DoSleep( appData.delayAfterQuit );
10743             DestroyChildProcess(first.pr, first.useSigterm);
10744         }
10745         first.pr = NoProc;
10746     }
10747     if (second.reuse) {
10748         /* Put second chess program into idle state */
10749         if (second.pr != NoProc &&
10750             gameMode == TwoMachinesPlay) {
10751             SendToProgram("force\n", &second);
10752             if (second.usePing) {
10753               char buf[MSG_SIZ];
10754               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10755               SendToProgram(buf, &second);
10756             }
10757         }
10758     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10759         /* Kill off second chess program */
10760         if (second.isr != NULL)
10761           RemoveInputSource(second.isr);
10762         second.isr = NULL;
10763
10764         if (second.pr != NoProc) {
10765             DoSleep( appData.delayBeforeQuit );
10766             SendToProgram("quit\n", &second);
10767             DoSleep( appData.delayAfterQuit );
10768             DestroyChildProcess(second.pr, second.useSigterm);
10769         }
10770         second.pr = NoProc;
10771     }
10772
10773     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10774         char resChar = '=';
10775         switch (result) {
10776         case WhiteWins:
10777           resChar = '+';
10778           if (first.twoMachinesColor[0] == 'w') {
10779             first.matchWins++;
10780           } else {
10781             second.matchWins++;
10782           }
10783           break;
10784         case BlackWins:
10785           resChar = '-';
10786           if (first.twoMachinesColor[0] == 'b') {
10787             first.matchWins++;
10788           } else {
10789             second.matchWins++;
10790           }
10791           break;
10792         case GameUnfinished:
10793           resChar = ' ';
10794         default:
10795           break;
10796         }
10797
10798         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10799         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10800             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10801             ReserveGame(nextGame, resChar); // sets nextGame
10802             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10803             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10804         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10805
10806         if (nextGame <= appData.matchGames && !abortMatch) {
10807             gameMode = nextGameMode;
10808             matchGame = nextGame; // this will be overruled in tourney mode!
10809             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10810             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10811             endingGame = 0; /* [HGM] crash */
10812             return;
10813         } else {
10814             gameMode = nextGameMode;
10815             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10816                      first.tidy, second.tidy,
10817                      first.matchWins, second.matchWins,
10818                      appData.matchGames - (first.matchWins + second.matchWins));
10819             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10820             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10821             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10822             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10823                 first.twoMachinesColor = "black\n";
10824                 second.twoMachinesColor = "white\n";
10825             } else {
10826                 first.twoMachinesColor = "white\n";
10827                 second.twoMachinesColor = "black\n";
10828             }
10829         }
10830     }
10831     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10832         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10833       ExitAnalyzeMode();
10834     gameMode = nextGameMode;
10835     ModeHighlight();
10836     endingGame = 0;  /* [HGM] crash */
10837     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10838         if(matchMode == TRUE) { // match through command line: exit with or without popup
10839             if(ranking) {
10840                 ToNrEvent(forwardMostMove);
10841                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10842                 else ExitEvent(0);
10843             } else DisplayFatalError(buf, 0, 0);
10844         } else { // match through menu; just stop, with or without popup
10845             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10846             ModeHighlight();
10847             if(ranking){
10848                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10849             } else DisplayNote(buf);
10850       }
10851       if(ranking) free(ranking);
10852     }
10853 }
10854
10855 /* Assumes program was just initialized (initString sent).
10856    Leaves program in force mode. */
10857 void
10858 FeedMovesToProgram (ChessProgramState *cps, int upto)
10859 {
10860     int i;
10861
10862     if (appData.debugMode)
10863       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10864               startedFromSetupPosition ? "position and " : "",
10865               backwardMostMove, upto, cps->which);
10866     if(currentlyInitializedVariant != gameInfo.variant) {
10867       char buf[MSG_SIZ];
10868         // [HGM] variantswitch: make engine aware of new variant
10869         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10870                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10871         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10872         SendToProgram(buf, cps);
10873         currentlyInitializedVariant = gameInfo.variant;
10874     }
10875     SendToProgram("force\n", cps);
10876     if (startedFromSetupPosition) {
10877         SendBoard(cps, backwardMostMove);
10878     if (appData.debugMode) {
10879         fprintf(debugFP, "feedMoves\n");
10880     }
10881     }
10882     for (i = backwardMostMove; i < upto; i++) {
10883         SendMoveToProgram(i, cps);
10884     }
10885 }
10886
10887
10888 int
10889 ResurrectChessProgram ()
10890 {
10891      /* The chess program may have exited.
10892         If so, restart it and feed it all the moves made so far. */
10893     static int doInit = 0;
10894
10895     if (appData.noChessProgram) return 1;
10896
10897     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10898         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10899         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10900         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10901     } else {
10902         if (first.pr != NoProc) return 1;
10903         StartChessProgram(&first);
10904     }
10905     InitChessProgram(&first, FALSE);
10906     FeedMovesToProgram(&first, currentMove);
10907
10908     if (!first.sendTime) {
10909         /* can't tell gnuchess what its clock should read,
10910            so we bow to its notion. */
10911         ResetClocks();
10912         timeRemaining[0][currentMove] = whiteTimeRemaining;
10913         timeRemaining[1][currentMove] = blackTimeRemaining;
10914     }
10915
10916     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10917                 appData.icsEngineAnalyze) && first.analysisSupport) {
10918       SendToProgram("analyze\n", &first);
10919       first.analyzing = TRUE;
10920     }
10921     return 1;
10922 }
10923
10924 /*
10925  * Button procedures
10926  */
10927 void
10928 Reset (int redraw, int init)
10929 {
10930     int i;
10931
10932     if (appData.debugMode) {
10933         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10934                 redraw, init, gameMode);
10935     }
10936     CleanupTail(); // [HGM] vari: delete any stored variations
10937     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10938     pausing = pauseExamInvalid = FALSE;
10939     startedFromSetupPosition = blackPlaysFirst = FALSE;
10940     firstMove = TRUE;
10941     whiteFlag = blackFlag = FALSE;
10942     userOfferedDraw = FALSE;
10943     hintRequested = bookRequested = FALSE;
10944     first.maybeThinking = FALSE;
10945     second.maybeThinking = FALSE;
10946     first.bookSuspend = FALSE; // [HGM] book
10947     second.bookSuspend = FALSE;
10948     thinkOutput[0] = NULLCHAR;
10949     lastHint[0] = NULLCHAR;
10950     ClearGameInfo(&gameInfo);
10951     gameInfo.variant = StringToVariant(appData.variant);
10952     ics_user_moved = ics_clock_paused = FALSE;
10953     ics_getting_history = H_FALSE;
10954     ics_gamenum = -1;
10955     white_holding[0] = black_holding[0] = NULLCHAR;
10956     ClearProgramStats();
10957     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10958
10959     ResetFrontEnd();
10960     ClearHighlights();
10961     flipView = appData.flipView;
10962     ClearPremoveHighlights();
10963     gotPremove = FALSE;
10964     alarmSounded = FALSE;
10965
10966     GameEnds(EndOfFile, NULL, GE_PLAYER);
10967     if(appData.serverMovesName != NULL) {
10968         /* [HGM] prepare to make moves file for broadcasting */
10969         clock_t t = clock();
10970         if(serverMoves != NULL) fclose(serverMoves);
10971         serverMoves = fopen(appData.serverMovesName, "r");
10972         if(serverMoves != NULL) {
10973             fclose(serverMoves);
10974             /* delay 15 sec before overwriting, so all clients can see end */
10975             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10976         }
10977         serverMoves = fopen(appData.serverMovesName, "w");
10978     }
10979
10980     ExitAnalyzeMode();
10981     gameMode = BeginningOfGame;
10982     ModeHighlight();
10983     if(appData.icsActive) gameInfo.variant = VariantNormal;
10984     currentMove = forwardMostMove = backwardMostMove = 0;
10985     MarkTargetSquares(1);
10986     InitPosition(redraw);
10987     for (i = 0; i < MAX_MOVES; i++) {
10988         if (commentList[i] != NULL) {
10989             free(commentList[i]);
10990             commentList[i] = NULL;
10991         }
10992     }
10993     ResetClocks();
10994     timeRemaining[0][0] = whiteTimeRemaining;
10995     timeRemaining[1][0] = blackTimeRemaining;
10996
10997     if (first.pr == NoProc) {
10998         StartChessProgram(&first);
10999     }
11000     if (init) {
11001             InitChessProgram(&first, startedFromSetupPosition);
11002     }
11003     DisplayTitle("");
11004     DisplayMessage("", "");
11005     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11006     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11007     ClearMap();        // [HGM] exclude: invalidate map
11008 }
11009
11010 void
11011 AutoPlayGameLoop ()
11012 {
11013     for (;;) {
11014         if (!AutoPlayOneMove())
11015           return;
11016         if (matchMode || appData.timeDelay == 0)
11017           continue;
11018         if (appData.timeDelay < 0)
11019           return;
11020         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11021         break;
11022     }
11023 }
11024
11025 void
11026 AnalyzeNextGame()
11027 {
11028     ReloadGame(1); // next game
11029 }
11030
11031 int
11032 AutoPlayOneMove ()
11033 {
11034     int fromX, fromY, toX, toY;
11035
11036     if (appData.debugMode) {
11037       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11038     }
11039
11040     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11041       return FALSE;
11042
11043     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11044       pvInfoList[currentMove].depth = programStats.depth;
11045       pvInfoList[currentMove].score = programStats.score;
11046       pvInfoList[currentMove].time  = 0;
11047       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11048     }
11049
11050     if (currentMove >= forwardMostMove) {
11051       if(gameMode == AnalyzeFile) {
11052           if(appData.loadGameIndex == -1) {
11053             GameEnds(EndOfFile, NULL, GE_FILE);
11054           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11055           } else {
11056           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11057         }
11058       }
11059 //      gameMode = EndOfGame;
11060 //      ModeHighlight();
11061
11062       /* [AS] Clear current move marker at the end of a game */
11063       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11064
11065       return FALSE;
11066     }
11067
11068     toX = moveList[currentMove][2] - AAA;
11069     toY = moveList[currentMove][3] - ONE;
11070
11071     if (moveList[currentMove][1] == '@') {
11072         if (appData.highlightLastMove) {
11073             SetHighlights(-1, -1, toX, toY);
11074         }
11075     } else {
11076         fromX = moveList[currentMove][0] - AAA;
11077         fromY = moveList[currentMove][1] - ONE;
11078
11079         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11080
11081         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11082
11083         if (appData.highlightLastMove) {
11084             SetHighlights(fromX, fromY, toX, toY);
11085         }
11086     }
11087     DisplayMove(currentMove);
11088     SendMoveToProgram(currentMove++, &first);
11089     DisplayBothClocks();
11090     DrawPosition(FALSE, boards[currentMove]);
11091     // [HGM] PV info: always display, routine tests if empty
11092     DisplayComment(currentMove - 1, commentList[currentMove]);
11093     return TRUE;
11094 }
11095
11096
11097 int
11098 LoadGameOneMove (ChessMove readAhead)
11099 {
11100     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11101     char promoChar = NULLCHAR;
11102     ChessMove moveType;
11103     char move[MSG_SIZ];
11104     char *p, *q;
11105
11106     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11107         gameMode != AnalyzeMode && gameMode != Training) {
11108         gameFileFP = NULL;
11109         return FALSE;
11110     }
11111
11112     yyboardindex = forwardMostMove;
11113     if (readAhead != EndOfFile) {
11114       moveType = readAhead;
11115     } else {
11116       if (gameFileFP == NULL)
11117           return FALSE;
11118       moveType = (ChessMove) Myylex();
11119     }
11120
11121     done = FALSE;
11122     switch (moveType) {
11123       case Comment:
11124         if (appData.debugMode)
11125           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11126         p = yy_text;
11127
11128         /* append the comment but don't display it */
11129         AppendComment(currentMove, p, FALSE);
11130         return TRUE;
11131
11132       case WhiteCapturesEnPassant:
11133       case BlackCapturesEnPassant:
11134       case WhitePromotion:
11135       case BlackPromotion:
11136       case WhiteNonPromotion:
11137       case BlackNonPromotion:
11138       case NormalMove:
11139       case WhiteKingSideCastle:
11140       case WhiteQueenSideCastle:
11141       case BlackKingSideCastle:
11142       case BlackQueenSideCastle:
11143       case WhiteKingSideCastleWild:
11144       case WhiteQueenSideCastleWild:
11145       case BlackKingSideCastleWild:
11146       case BlackQueenSideCastleWild:
11147       /* PUSH Fabien */
11148       case WhiteHSideCastleFR:
11149       case WhiteASideCastleFR:
11150       case BlackHSideCastleFR:
11151       case BlackASideCastleFR:
11152       /* POP Fabien */
11153         if (appData.debugMode)
11154           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11155         fromX = currentMoveString[0] - AAA;
11156         fromY = currentMoveString[1] - ONE;
11157         toX = currentMoveString[2] - AAA;
11158         toY = currentMoveString[3] - ONE;
11159         promoChar = currentMoveString[4];
11160         break;
11161
11162       case WhiteDrop:
11163       case BlackDrop:
11164         if (appData.debugMode)
11165           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11166         fromX = moveType == WhiteDrop ?
11167           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11168         (int) CharToPiece(ToLower(currentMoveString[0]));
11169         fromY = DROP_RANK;
11170         toX = currentMoveString[2] - AAA;
11171         toY = currentMoveString[3] - ONE;
11172         break;
11173
11174       case WhiteWins:
11175       case BlackWins:
11176       case GameIsDrawn:
11177       case GameUnfinished:
11178         if (appData.debugMode)
11179           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11180         p = strchr(yy_text, '{');
11181         if (p == NULL) p = strchr(yy_text, '(');
11182         if (p == NULL) {
11183             p = yy_text;
11184             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11185         } else {
11186             q = strchr(p, *p == '{' ? '}' : ')');
11187             if (q != NULL) *q = NULLCHAR;
11188             p++;
11189         }
11190         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11191         GameEnds(moveType, p, GE_FILE);
11192         done = TRUE;
11193         if (cmailMsgLoaded) {
11194             ClearHighlights();
11195             flipView = WhiteOnMove(currentMove);
11196             if (moveType == GameUnfinished) flipView = !flipView;
11197             if (appData.debugMode)
11198               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11199         }
11200         break;
11201
11202       case EndOfFile:
11203         if (appData.debugMode)
11204           fprintf(debugFP, "Parser hit end of file\n");
11205         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11206           case MT_NONE:
11207           case MT_CHECK:
11208             break;
11209           case MT_CHECKMATE:
11210           case MT_STAINMATE:
11211             if (WhiteOnMove(currentMove)) {
11212                 GameEnds(BlackWins, "Black mates", GE_FILE);
11213             } else {
11214                 GameEnds(WhiteWins, "White mates", GE_FILE);
11215             }
11216             break;
11217           case MT_STALEMATE:
11218             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11219             break;
11220         }
11221         done = TRUE;
11222         break;
11223
11224       case MoveNumberOne:
11225         if (lastLoadGameStart == GNUChessGame) {
11226             /* GNUChessGames have numbers, but they aren't move numbers */
11227             if (appData.debugMode)
11228               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11229                       yy_text, (int) moveType);
11230             return LoadGameOneMove(EndOfFile); /* tail recursion */
11231         }
11232         /* else fall thru */
11233
11234       case XBoardGame:
11235       case GNUChessGame:
11236       case PGNTag:
11237         /* Reached start of next game in file */
11238         if (appData.debugMode)
11239           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11240         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11241           case MT_NONE:
11242           case MT_CHECK:
11243             break;
11244           case MT_CHECKMATE:
11245           case MT_STAINMATE:
11246             if (WhiteOnMove(currentMove)) {
11247                 GameEnds(BlackWins, "Black mates", GE_FILE);
11248             } else {
11249                 GameEnds(WhiteWins, "White mates", GE_FILE);
11250             }
11251             break;
11252           case MT_STALEMATE:
11253             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11254             break;
11255         }
11256         done = TRUE;
11257         break;
11258
11259       case PositionDiagram:     /* should not happen; ignore */
11260       case ElapsedTime:         /* ignore */
11261       case NAG:                 /* ignore */
11262         if (appData.debugMode)
11263           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11264                   yy_text, (int) moveType);
11265         return LoadGameOneMove(EndOfFile); /* tail recursion */
11266
11267       case IllegalMove:
11268         if (appData.testLegality) {
11269             if (appData.debugMode)
11270               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11271             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11272                     (forwardMostMove / 2) + 1,
11273                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11274             DisplayError(move, 0);
11275             done = TRUE;
11276         } else {
11277             if (appData.debugMode)
11278               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11279                       yy_text, currentMoveString);
11280             fromX = currentMoveString[0] - AAA;
11281             fromY = currentMoveString[1] - ONE;
11282             toX = currentMoveString[2] - AAA;
11283             toY = currentMoveString[3] - ONE;
11284             promoChar = currentMoveString[4];
11285         }
11286         break;
11287
11288       case AmbiguousMove:
11289         if (appData.debugMode)
11290           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11291         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11292                 (forwardMostMove / 2) + 1,
11293                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11294         DisplayError(move, 0);
11295         done = TRUE;
11296         break;
11297
11298       default:
11299       case ImpossibleMove:
11300         if (appData.debugMode)
11301           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11302         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11303                 (forwardMostMove / 2) + 1,
11304                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11305         DisplayError(move, 0);
11306         done = TRUE;
11307         break;
11308     }
11309
11310     if (done) {
11311         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11312             DrawPosition(FALSE, boards[currentMove]);
11313             DisplayBothClocks();
11314             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11315               DisplayComment(currentMove - 1, commentList[currentMove]);
11316         }
11317         (void) StopLoadGameTimer();
11318         gameFileFP = NULL;
11319         cmailOldMove = forwardMostMove;
11320         return FALSE;
11321     } else {
11322         /* currentMoveString is set as a side-effect of yylex */
11323
11324         thinkOutput[0] = NULLCHAR;
11325         MakeMove(fromX, fromY, toX, toY, promoChar);
11326         currentMove = forwardMostMove;
11327         return TRUE;
11328     }
11329 }
11330
11331 /* Load the nth game from the given file */
11332 int
11333 LoadGameFromFile (char *filename, int n, char *title, int useList)
11334 {
11335     FILE *f;
11336     char buf[MSG_SIZ];
11337
11338     if (strcmp(filename, "-") == 0) {
11339         f = stdin;
11340         title = "stdin";
11341     } else {
11342         f = fopen(filename, "rb");
11343         if (f == NULL) {
11344           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11345             DisplayError(buf, errno);
11346             return FALSE;
11347         }
11348     }
11349     if (fseek(f, 0, 0) == -1) {
11350         /* f is not seekable; probably a pipe */
11351         useList = FALSE;
11352     }
11353     if (useList && n == 0) {
11354         int error = GameListBuild(f);
11355         if (error) {
11356             DisplayError(_("Cannot build game list"), error);
11357         } else if (!ListEmpty(&gameList) &&
11358                    ((ListGame *) gameList.tailPred)->number > 1) {
11359             GameListPopUp(f, title);
11360             return TRUE;
11361         }
11362         GameListDestroy();
11363         n = 1;
11364     }
11365     if (n == 0) n = 1;
11366     return LoadGame(f, n, title, FALSE);
11367 }
11368
11369
11370 void
11371 MakeRegisteredMove ()
11372 {
11373     int fromX, fromY, toX, toY;
11374     char promoChar;
11375     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11376         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11377           case CMAIL_MOVE:
11378           case CMAIL_DRAW:
11379             if (appData.debugMode)
11380               fprintf(debugFP, "Restoring %s for game %d\n",
11381                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11382
11383             thinkOutput[0] = NULLCHAR;
11384             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11385             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11386             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11387             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11388             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11389             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11390             MakeMove(fromX, fromY, toX, toY, promoChar);
11391             ShowMove(fromX, fromY, toX, toY);
11392
11393             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11394               case MT_NONE:
11395               case MT_CHECK:
11396                 break;
11397
11398               case MT_CHECKMATE:
11399               case MT_STAINMATE:
11400                 if (WhiteOnMove(currentMove)) {
11401                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11402                 } else {
11403                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11404                 }
11405                 break;
11406
11407               case MT_STALEMATE:
11408                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11409                 break;
11410             }
11411
11412             break;
11413
11414           case CMAIL_RESIGN:
11415             if (WhiteOnMove(currentMove)) {
11416                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11417             } else {
11418                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11419             }
11420             break;
11421
11422           case CMAIL_ACCEPT:
11423             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11424             break;
11425
11426           default:
11427             break;
11428         }
11429     }
11430
11431     return;
11432 }
11433
11434 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11435 int
11436 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11437 {
11438     int retVal;
11439
11440     if (gameNumber > nCmailGames) {
11441         DisplayError(_("No more games in this message"), 0);
11442         return FALSE;
11443     }
11444     if (f == lastLoadGameFP) {
11445         int offset = gameNumber - lastLoadGameNumber;
11446         if (offset == 0) {
11447             cmailMsg[0] = NULLCHAR;
11448             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11449                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11450                 nCmailMovesRegistered--;
11451             }
11452             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11453             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11454                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11455             }
11456         } else {
11457             if (! RegisterMove()) return FALSE;
11458         }
11459     }
11460
11461     retVal = LoadGame(f, gameNumber, title, useList);
11462
11463     /* Make move registered during previous look at this game, if any */
11464     MakeRegisteredMove();
11465
11466     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11467         commentList[currentMove]
11468           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11469         DisplayComment(currentMove - 1, commentList[currentMove]);
11470     }
11471
11472     return retVal;
11473 }
11474
11475 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11476 int
11477 ReloadGame (int offset)
11478 {
11479     int gameNumber = lastLoadGameNumber + offset;
11480     if (lastLoadGameFP == NULL) {
11481         DisplayError(_("No game has been loaded yet"), 0);
11482         return FALSE;
11483     }
11484     if (gameNumber <= 0) {
11485         DisplayError(_("Can't back up any further"), 0);
11486         return FALSE;
11487     }
11488     if (cmailMsgLoaded) {
11489         return CmailLoadGame(lastLoadGameFP, gameNumber,
11490                              lastLoadGameTitle, lastLoadGameUseList);
11491     } else {
11492         return LoadGame(lastLoadGameFP, gameNumber,
11493                         lastLoadGameTitle, lastLoadGameUseList);
11494     }
11495 }
11496
11497 int keys[EmptySquare+1];
11498
11499 int
11500 PositionMatches (Board b1, Board b2)
11501 {
11502     int r, f, sum=0;
11503     switch(appData.searchMode) {
11504         case 1: return CompareWithRights(b1, b2);
11505         case 2:
11506             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11507                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11508             }
11509             return TRUE;
11510         case 3:
11511             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11512               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11513                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11514             }
11515             return sum==0;
11516         case 4:
11517             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11518                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11519             }
11520             return sum==0;
11521     }
11522     return TRUE;
11523 }
11524
11525 #define Q_PROMO  4
11526 #define Q_EP     3
11527 #define Q_BCASTL 2
11528 #define Q_WCASTL 1
11529
11530 int pieceList[256], quickBoard[256];
11531 ChessSquare pieceType[256] = { EmptySquare };
11532 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11533 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11534 int soughtTotal, turn;
11535 Boolean epOK, flipSearch;
11536
11537 typedef struct {
11538     unsigned char piece, to;
11539 } Move;
11540
11541 #define DSIZE (250000)
11542
11543 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11544 Move *moveDatabase = initialSpace;
11545 unsigned int movePtr, dataSize = DSIZE;
11546
11547 int
11548 MakePieceList (Board board, int *counts)
11549 {
11550     int r, f, n=Q_PROMO, total=0;
11551     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11552     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11553         int sq = f + (r<<4);
11554         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11555             quickBoard[sq] = ++n;
11556             pieceList[n] = sq;
11557             pieceType[n] = board[r][f];
11558             counts[board[r][f]]++;
11559             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11560             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11561             total++;
11562         }
11563     }
11564     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11565     return total;
11566 }
11567
11568 void
11569 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11570 {
11571     int sq = fromX + (fromY<<4);
11572     int piece = quickBoard[sq];
11573     quickBoard[sq] = 0;
11574     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11575     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11576         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11577         moveDatabase[movePtr++].piece = Q_WCASTL;
11578         quickBoard[sq] = piece;
11579         piece = quickBoard[from]; quickBoard[from] = 0;
11580         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11581     } else
11582     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11583         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11584         moveDatabase[movePtr++].piece = Q_BCASTL;
11585         quickBoard[sq] = piece;
11586         piece = quickBoard[from]; quickBoard[from] = 0;
11587         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11588     } else
11589     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11590         quickBoard[(fromY<<4)+toX] = 0;
11591         moveDatabase[movePtr].piece = Q_EP;
11592         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11593         moveDatabase[movePtr].to = sq;
11594     } else
11595     if(promoPiece != pieceType[piece]) {
11596         moveDatabase[movePtr++].piece = Q_PROMO;
11597         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11598     }
11599     moveDatabase[movePtr].piece = piece;
11600     quickBoard[sq] = piece;
11601     movePtr++;
11602 }
11603
11604 int
11605 PackGame (Board board)
11606 {
11607     Move *newSpace = NULL;
11608     moveDatabase[movePtr].piece = 0; // terminate previous game
11609     if(movePtr > dataSize) {
11610         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11611         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11612         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11613         if(newSpace) {
11614             int i;
11615             Move *p = moveDatabase, *q = newSpace;
11616             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11617             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11618             moveDatabase = newSpace;
11619         } else { // calloc failed, we must be out of memory. Too bad...
11620             dataSize = 0; // prevent calloc events for all subsequent games
11621             return 0;     // and signal this one isn't cached
11622         }
11623     }
11624     movePtr++;
11625     MakePieceList(board, counts);
11626     return movePtr;
11627 }
11628
11629 int
11630 QuickCompare (Board board, int *minCounts, int *maxCounts)
11631 {   // compare according to search mode
11632     int r, f;
11633     switch(appData.searchMode)
11634     {
11635       case 1: // exact position match
11636         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11637         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11638             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11639         }
11640         break;
11641       case 2: // can have extra material on empty squares
11642         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11643             if(board[r][f] == EmptySquare) continue;
11644             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11645         }
11646         break;
11647       case 3: // material with exact Pawn structure
11648         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11649             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11650             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11651         } // fall through to material comparison
11652       case 4: // exact material
11653         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11654         break;
11655       case 6: // material range with given imbalance
11656         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11657         // fall through to range comparison
11658       case 5: // material range
11659         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11660     }
11661     return TRUE;
11662 }
11663
11664 int
11665 QuickScan (Board board, Move *move)
11666 {   // reconstruct game,and compare all positions in it
11667     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11668     do {
11669         int piece = move->piece;
11670         int to = move->to, from = pieceList[piece];
11671         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11672           if(!piece) return -1;
11673           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11674             piece = (++move)->piece;
11675             from = pieceList[piece];
11676             counts[pieceType[piece]]--;
11677             pieceType[piece] = (ChessSquare) move->to;
11678             counts[move->to]++;
11679           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11680             counts[pieceType[quickBoard[to]]]--;
11681             quickBoard[to] = 0; total--;
11682             move++;
11683             continue;
11684           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11685             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11686             from  = pieceList[piece]; // so this must be King
11687             quickBoard[from] = 0;
11688             pieceList[piece] = to;
11689             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11690             quickBoard[from] = 0; // rook
11691             quickBoard[to] = piece;
11692             to = move->to; piece = move->piece;
11693             goto aftercastle;
11694           }
11695         }
11696         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11697         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11698         quickBoard[from] = 0;
11699       aftercastle:
11700         quickBoard[to] = piece;
11701         pieceList[piece] = to;
11702         cnt++; turn ^= 3;
11703         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11704            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11705            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11706                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11707           ) {
11708             static int lastCounts[EmptySquare+1];
11709             int i;
11710             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11711             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11712         } else stretch = 0;
11713         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11714         move++;
11715     } while(1);
11716 }
11717
11718 void
11719 InitSearch ()
11720 {
11721     int r, f;
11722     flipSearch = FALSE;
11723     CopyBoard(soughtBoard, boards[currentMove]);
11724     soughtTotal = MakePieceList(soughtBoard, maxSought);
11725     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11726     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11727     CopyBoard(reverseBoard, boards[currentMove]);
11728     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11729         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11730         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11731         reverseBoard[r][f] = piece;
11732     }
11733     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11734     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11735     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11736                  || (boards[currentMove][CASTLING][2] == NoRights || 
11737                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11738                  && (boards[currentMove][CASTLING][5] == NoRights || 
11739                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11740       ) {
11741         flipSearch = TRUE;
11742         CopyBoard(flipBoard, soughtBoard);
11743         CopyBoard(rotateBoard, reverseBoard);
11744         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11745             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11746             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11747         }
11748     }
11749     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11750     if(appData.searchMode >= 5) {
11751         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11752         MakePieceList(soughtBoard, minSought);
11753         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11754     }
11755     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11756         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11757 }
11758
11759 GameInfo dummyInfo;
11760
11761 int
11762 GameContainsPosition (FILE *f, ListGame *lg)
11763 {
11764     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11765     int fromX, fromY, toX, toY;
11766     char promoChar;
11767     static int initDone=FALSE;
11768
11769     // weed out games based on numerical tag comparison
11770     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11771     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11772     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11773     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11774     if(!initDone) {
11775         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11776         initDone = TRUE;
11777     }
11778     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11779     else CopyBoard(boards[scratch], initialPosition); // default start position
11780     if(lg->moves) {
11781         turn = btm + 1;
11782         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11783         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11784     }
11785     if(btm) plyNr++;
11786     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11787     fseek(f, lg->offset, 0);
11788     yynewfile(f);
11789     while(1) {
11790         yyboardindex = scratch;
11791         quickFlag = plyNr+1;
11792         next = Myylex();
11793         quickFlag = 0;
11794         switch(next) {
11795             case PGNTag:
11796                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11797             default:
11798                 continue;
11799
11800             case XBoardGame:
11801             case GNUChessGame:
11802                 if(plyNr) return -1; // after we have seen moves, this is for new game
11803               continue;
11804
11805             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11806             case ImpossibleMove:
11807             case WhiteWins: // game ends here with these four
11808             case BlackWins:
11809             case GameIsDrawn:
11810             case GameUnfinished:
11811                 return -1;
11812
11813             case IllegalMove:
11814                 if(appData.testLegality) return -1;
11815             case WhiteCapturesEnPassant:
11816             case BlackCapturesEnPassant:
11817             case WhitePromotion:
11818             case BlackPromotion:
11819             case WhiteNonPromotion:
11820             case BlackNonPromotion:
11821             case NormalMove:
11822             case WhiteKingSideCastle:
11823             case WhiteQueenSideCastle:
11824             case BlackKingSideCastle:
11825             case BlackQueenSideCastle:
11826             case WhiteKingSideCastleWild:
11827             case WhiteQueenSideCastleWild:
11828             case BlackKingSideCastleWild:
11829             case BlackQueenSideCastleWild:
11830             case WhiteHSideCastleFR:
11831             case WhiteASideCastleFR:
11832             case BlackHSideCastleFR:
11833             case BlackASideCastleFR:
11834                 fromX = currentMoveString[0] - AAA;
11835                 fromY = currentMoveString[1] - ONE;
11836                 toX = currentMoveString[2] - AAA;
11837                 toY = currentMoveString[3] - ONE;
11838                 promoChar = currentMoveString[4];
11839                 break;
11840             case WhiteDrop:
11841             case BlackDrop:
11842                 fromX = next == WhiteDrop ?
11843                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11844                   (int) CharToPiece(ToLower(currentMoveString[0]));
11845                 fromY = DROP_RANK;
11846                 toX = currentMoveString[2] - AAA;
11847                 toY = currentMoveString[3] - ONE;
11848                 promoChar = 0;
11849                 break;
11850         }
11851         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11852         plyNr++;
11853         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11854         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11855         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11856         if(appData.findMirror) {
11857             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11858             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11859         }
11860     }
11861 }
11862
11863 /* Load the nth game from open file f */
11864 int
11865 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11866 {
11867     ChessMove cm;
11868     char buf[MSG_SIZ];
11869     int gn = gameNumber;
11870     ListGame *lg = NULL;
11871     int numPGNTags = 0;
11872     int err, pos = -1;
11873     GameMode oldGameMode;
11874     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11875
11876     if (appData.debugMode)
11877         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11878
11879     if (gameMode == Training )
11880         SetTrainingModeOff();
11881
11882     oldGameMode = gameMode;
11883     if (gameMode != BeginningOfGame) {
11884       Reset(FALSE, TRUE);
11885     }
11886
11887     gameFileFP = f;
11888     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11889         fclose(lastLoadGameFP);
11890     }
11891
11892     if (useList) {
11893         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11894
11895         if (lg) {
11896             fseek(f, lg->offset, 0);
11897             GameListHighlight(gameNumber);
11898             pos = lg->position;
11899             gn = 1;
11900         }
11901         else {
11902             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11903               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11904             else
11905             DisplayError(_("Game number out of range"), 0);
11906             return FALSE;
11907         }
11908     } else {
11909         GameListDestroy();
11910         if (fseek(f, 0, 0) == -1) {
11911             if (f == lastLoadGameFP ?
11912                 gameNumber == lastLoadGameNumber + 1 :
11913                 gameNumber == 1) {
11914                 gn = 1;
11915             } else {
11916                 DisplayError(_("Can't seek on game file"), 0);
11917                 return FALSE;
11918             }
11919         }
11920     }
11921     lastLoadGameFP = f;
11922     lastLoadGameNumber = gameNumber;
11923     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11924     lastLoadGameUseList = useList;
11925
11926     yynewfile(f);
11927
11928     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11929       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11930                 lg->gameInfo.black);
11931             DisplayTitle(buf);
11932     } else if (*title != NULLCHAR) {
11933         if (gameNumber > 1) {
11934           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11935             DisplayTitle(buf);
11936         } else {
11937             DisplayTitle(title);
11938         }
11939     }
11940
11941     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11942         gameMode = PlayFromGameFile;
11943         ModeHighlight();
11944     }
11945
11946     currentMove = forwardMostMove = backwardMostMove = 0;
11947     CopyBoard(boards[0], initialPosition);
11948     StopClocks();
11949
11950     /*
11951      * Skip the first gn-1 games in the file.
11952      * Also skip over anything that precedes an identifiable
11953      * start of game marker, to avoid being confused by
11954      * garbage at the start of the file.  Currently
11955      * recognized start of game markers are the move number "1",
11956      * the pattern "gnuchess .* game", the pattern
11957      * "^[#;%] [^ ]* game file", and a PGN tag block.
11958      * A game that starts with one of the latter two patterns
11959      * will also have a move number 1, possibly
11960      * following a position diagram.
11961      * 5-4-02: Let's try being more lenient and allowing a game to
11962      * start with an unnumbered move.  Does that break anything?
11963      */
11964     cm = lastLoadGameStart = EndOfFile;
11965     while (gn > 0) {
11966         yyboardindex = forwardMostMove;
11967         cm = (ChessMove) Myylex();
11968         switch (cm) {
11969           case EndOfFile:
11970             if (cmailMsgLoaded) {
11971                 nCmailGames = CMAIL_MAX_GAMES - gn;
11972             } else {
11973                 Reset(TRUE, TRUE);
11974                 DisplayError(_("Game not found in file"), 0);
11975             }
11976             return FALSE;
11977
11978           case GNUChessGame:
11979           case XBoardGame:
11980             gn--;
11981             lastLoadGameStart = cm;
11982             break;
11983
11984           case MoveNumberOne:
11985             switch (lastLoadGameStart) {
11986               case GNUChessGame:
11987               case XBoardGame:
11988               case PGNTag:
11989                 break;
11990               case MoveNumberOne:
11991               case EndOfFile:
11992                 gn--;           /* count this game */
11993                 lastLoadGameStart = cm;
11994                 break;
11995               default:
11996                 /* impossible */
11997                 break;
11998             }
11999             break;
12000
12001           case PGNTag:
12002             switch (lastLoadGameStart) {
12003               case GNUChessGame:
12004               case PGNTag:
12005               case MoveNumberOne:
12006               case EndOfFile:
12007                 gn--;           /* count this game */
12008                 lastLoadGameStart = cm;
12009                 break;
12010               case XBoardGame:
12011                 lastLoadGameStart = cm; /* game counted already */
12012                 break;
12013               default:
12014                 /* impossible */
12015                 break;
12016             }
12017             if (gn > 0) {
12018                 do {
12019                     yyboardindex = forwardMostMove;
12020                     cm = (ChessMove) Myylex();
12021                 } while (cm == PGNTag || cm == Comment);
12022             }
12023             break;
12024
12025           case WhiteWins:
12026           case BlackWins:
12027           case GameIsDrawn:
12028             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12029                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12030                     != CMAIL_OLD_RESULT) {
12031                     nCmailResults ++ ;
12032                     cmailResult[  CMAIL_MAX_GAMES
12033                                 - gn - 1] = CMAIL_OLD_RESULT;
12034                 }
12035             }
12036             break;
12037
12038           case NormalMove:
12039             /* Only a NormalMove can be at the start of a game
12040              * without a position diagram. */
12041             if (lastLoadGameStart == EndOfFile ) {
12042               gn--;
12043               lastLoadGameStart = MoveNumberOne;
12044             }
12045             break;
12046
12047           default:
12048             break;
12049         }
12050     }
12051
12052     if (appData.debugMode)
12053       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12054
12055     if (cm == XBoardGame) {
12056         /* Skip any header junk before position diagram and/or move 1 */
12057         for (;;) {
12058             yyboardindex = forwardMostMove;
12059             cm = (ChessMove) Myylex();
12060
12061             if (cm == EndOfFile ||
12062                 cm == GNUChessGame || cm == XBoardGame) {
12063                 /* Empty game; pretend end-of-file and handle later */
12064                 cm = EndOfFile;
12065                 break;
12066             }
12067
12068             if (cm == MoveNumberOne || cm == PositionDiagram ||
12069                 cm == PGNTag || cm == Comment)
12070               break;
12071         }
12072     } else if (cm == GNUChessGame) {
12073         if (gameInfo.event != NULL) {
12074             free(gameInfo.event);
12075         }
12076         gameInfo.event = StrSave(yy_text);
12077     }
12078
12079     startedFromSetupPosition = FALSE;
12080     while (cm == PGNTag) {
12081         if (appData.debugMode)
12082           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12083         err = ParsePGNTag(yy_text, &gameInfo);
12084         if (!err) numPGNTags++;
12085
12086         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12087         if(gameInfo.variant != oldVariant) {
12088             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12089             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12090             InitPosition(TRUE);
12091             oldVariant = gameInfo.variant;
12092             if (appData.debugMode)
12093               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12094         }
12095
12096
12097         if (gameInfo.fen != NULL) {
12098           Board initial_position;
12099           startedFromSetupPosition = TRUE;
12100           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12101             Reset(TRUE, TRUE);
12102             DisplayError(_("Bad FEN position in file"), 0);
12103             return FALSE;
12104           }
12105           CopyBoard(boards[0], initial_position);
12106           if (blackPlaysFirst) {
12107             currentMove = forwardMostMove = backwardMostMove = 1;
12108             CopyBoard(boards[1], initial_position);
12109             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12110             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12111             timeRemaining[0][1] = whiteTimeRemaining;
12112             timeRemaining[1][1] = blackTimeRemaining;
12113             if (commentList[0] != NULL) {
12114               commentList[1] = commentList[0];
12115               commentList[0] = NULL;
12116             }
12117           } else {
12118             currentMove = forwardMostMove = backwardMostMove = 0;
12119           }
12120           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12121           {   int i;
12122               initialRulePlies = FENrulePlies;
12123               for( i=0; i< nrCastlingRights; i++ )
12124                   initialRights[i] = initial_position[CASTLING][i];
12125           }
12126           yyboardindex = forwardMostMove;
12127           free(gameInfo.fen);
12128           gameInfo.fen = NULL;
12129         }
12130
12131         yyboardindex = forwardMostMove;
12132         cm = (ChessMove) Myylex();
12133
12134         /* Handle comments interspersed among the tags */
12135         while (cm == Comment) {
12136             char *p;
12137             if (appData.debugMode)
12138               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12139             p = yy_text;
12140             AppendComment(currentMove, p, FALSE);
12141             yyboardindex = forwardMostMove;
12142             cm = (ChessMove) Myylex();
12143         }
12144     }
12145
12146     /* don't rely on existence of Event tag since if game was
12147      * pasted from clipboard the Event tag may not exist
12148      */
12149     if (numPGNTags > 0){
12150         char *tags;
12151         if (gameInfo.variant == VariantNormal) {
12152           VariantClass v = StringToVariant(gameInfo.event);
12153           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12154           if(v < VariantShogi) gameInfo.variant = v;
12155         }
12156         if (!matchMode) {
12157           if( appData.autoDisplayTags ) {
12158             tags = PGNTags(&gameInfo);
12159             TagsPopUp(tags, CmailMsg());
12160             free(tags);
12161           }
12162         }
12163     } else {
12164         /* Make something up, but don't display it now */
12165         SetGameInfo();
12166         TagsPopDown();
12167     }
12168
12169     if (cm == PositionDiagram) {
12170         int i, j;
12171         char *p;
12172         Board initial_position;
12173
12174         if (appData.debugMode)
12175           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12176
12177         if (!startedFromSetupPosition) {
12178             p = yy_text;
12179             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12180               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12181                 switch (*p) {
12182                   case '{':
12183                   case '[':
12184                   case '-':
12185                   case ' ':
12186                   case '\t':
12187                   case '\n':
12188                   case '\r':
12189                     break;
12190                   default:
12191                     initial_position[i][j++] = CharToPiece(*p);
12192                     break;
12193                 }
12194             while (*p == ' ' || *p == '\t' ||
12195                    *p == '\n' || *p == '\r') p++;
12196
12197             if (strncmp(p, "black", strlen("black"))==0)
12198               blackPlaysFirst = TRUE;
12199             else
12200               blackPlaysFirst = FALSE;
12201             startedFromSetupPosition = TRUE;
12202
12203             CopyBoard(boards[0], initial_position);
12204             if (blackPlaysFirst) {
12205                 currentMove = forwardMostMove = backwardMostMove = 1;
12206                 CopyBoard(boards[1], initial_position);
12207                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12208                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12209                 timeRemaining[0][1] = whiteTimeRemaining;
12210                 timeRemaining[1][1] = blackTimeRemaining;
12211                 if (commentList[0] != NULL) {
12212                     commentList[1] = commentList[0];
12213                     commentList[0] = NULL;
12214                 }
12215             } else {
12216                 currentMove = forwardMostMove = backwardMostMove = 0;
12217             }
12218         }
12219         yyboardindex = forwardMostMove;
12220         cm = (ChessMove) Myylex();
12221     }
12222
12223     if (first.pr == NoProc) {
12224         StartChessProgram(&first);
12225     }
12226     InitChessProgram(&first, FALSE);
12227     SendToProgram("force\n", &first);
12228     if (startedFromSetupPosition) {
12229         SendBoard(&first, forwardMostMove);
12230     if (appData.debugMode) {
12231         fprintf(debugFP, "Load Game\n");
12232     }
12233         DisplayBothClocks();
12234     }
12235
12236     /* [HGM] server: flag to write setup moves in broadcast file as one */
12237     loadFlag = appData.suppressLoadMoves;
12238
12239     while (cm == Comment) {
12240         char *p;
12241         if (appData.debugMode)
12242           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12243         p = yy_text;
12244         AppendComment(currentMove, p, FALSE);
12245         yyboardindex = forwardMostMove;
12246         cm = (ChessMove) Myylex();
12247     }
12248
12249     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12250         cm == WhiteWins || cm == BlackWins ||
12251         cm == GameIsDrawn || cm == GameUnfinished) {
12252         DisplayMessage("", _("No moves in game"));
12253         if (cmailMsgLoaded) {
12254             if (appData.debugMode)
12255               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12256             ClearHighlights();
12257             flipView = FALSE;
12258         }
12259         DrawPosition(FALSE, boards[currentMove]);
12260         DisplayBothClocks();
12261         gameMode = EditGame;
12262         ModeHighlight();
12263         gameFileFP = NULL;
12264         cmailOldMove = 0;
12265         return TRUE;
12266     }
12267
12268     // [HGM] PV info: routine tests if comment empty
12269     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12270         DisplayComment(currentMove - 1, commentList[currentMove]);
12271     }
12272     if (!matchMode && appData.timeDelay != 0)
12273       DrawPosition(FALSE, boards[currentMove]);
12274
12275     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12276       programStats.ok_to_send = 1;
12277     }
12278
12279     /* if the first token after the PGN tags is a move
12280      * and not move number 1, retrieve it from the parser
12281      */
12282     if (cm != MoveNumberOne)
12283         LoadGameOneMove(cm);
12284
12285     /* load the remaining moves from the file */
12286     while (LoadGameOneMove(EndOfFile)) {
12287       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12288       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12289     }
12290
12291     /* rewind to the start of the game */
12292     currentMove = backwardMostMove;
12293
12294     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12295
12296     if (oldGameMode == AnalyzeFile ||
12297         oldGameMode == AnalyzeMode) {
12298       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12299       keepInfo = 1;
12300       AnalyzeFileEvent();
12301       keepInfo = 0;
12302     }
12303
12304     if (!matchMode && pos > 0) {
12305         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12306     } else
12307     if (matchMode || appData.timeDelay == 0) {
12308       ToEndEvent();
12309     } else if (appData.timeDelay > 0) {
12310       AutoPlayGameLoop();
12311     }
12312
12313     if (appData.debugMode)
12314         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12315
12316     loadFlag = 0; /* [HGM] true game starts */
12317     return TRUE;
12318 }
12319
12320 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12321 int
12322 ReloadPosition (int offset)
12323 {
12324     int positionNumber = lastLoadPositionNumber + offset;
12325     if (lastLoadPositionFP == NULL) {
12326         DisplayError(_("No position has been loaded yet"), 0);
12327         return FALSE;
12328     }
12329     if (positionNumber <= 0) {
12330         DisplayError(_("Can't back up any further"), 0);
12331         return FALSE;
12332     }
12333     return LoadPosition(lastLoadPositionFP, positionNumber,
12334                         lastLoadPositionTitle);
12335 }
12336
12337 /* Load the nth position from the given file */
12338 int
12339 LoadPositionFromFile (char *filename, int n, char *title)
12340 {
12341     FILE *f;
12342     char buf[MSG_SIZ];
12343
12344     if (strcmp(filename, "-") == 0) {
12345         return LoadPosition(stdin, n, "stdin");
12346     } else {
12347         f = fopen(filename, "rb");
12348         if (f == NULL) {
12349             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12350             DisplayError(buf, errno);
12351             return FALSE;
12352         } else {
12353             return LoadPosition(f, n, title);
12354         }
12355     }
12356 }
12357
12358 /* Load the nth position from the given open file, and close it */
12359 int
12360 LoadPosition (FILE *f, int positionNumber, char *title)
12361 {
12362     char *p, line[MSG_SIZ];
12363     Board initial_position;
12364     int i, j, fenMode, pn;
12365
12366     if (gameMode == Training )
12367         SetTrainingModeOff();
12368
12369     if (gameMode != BeginningOfGame) {
12370         Reset(FALSE, TRUE);
12371     }
12372     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12373         fclose(lastLoadPositionFP);
12374     }
12375     if (positionNumber == 0) positionNumber = 1;
12376     lastLoadPositionFP = f;
12377     lastLoadPositionNumber = positionNumber;
12378     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12379     if (first.pr == NoProc && !appData.noChessProgram) {
12380       StartChessProgram(&first);
12381       InitChessProgram(&first, FALSE);
12382     }
12383     pn = positionNumber;
12384     if (positionNumber < 0) {
12385         /* Negative position number means to seek to that byte offset */
12386         if (fseek(f, -positionNumber, 0) == -1) {
12387             DisplayError(_("Can't seek on position file"), 0);
12388             return FALSE;
12389         };
12390         pn = 1;
12391     } else {
12392         if (fseek(f, 0, 0) == -1) {
12393             if (f == lastLoadPositionFP ?
12394                 positionNumber == lastLoadPositionNumber + 1 :
12395                 positionNumber == 1) {
12396                 pn = 1;
12397             } else {
12398                 DisplayError(_("Can't seek on position file"), 0);
12399                 return FALSE;
12400             }
12401         }
12402     }
12403     /* See if this file is FEN or old-style xboard */
12404     if (fgets(line, MSG_SIZ, f) == NULL) {
12405         DisplayError(_("Position not found in file"), 0);
12406         return FALSE;
12407     }
12408     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12409     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12410
12411     if (pn >= 2) {
12412         if (fenMode || line[0] == '#') pn--;
12413         while (pn > 0) {
12414             /* skip positions before number pn */
12415             if (fgets(line, MSG_SIZ, f) == NULL) {
12416                 Reset(TRUE, TRUE);
12417                 DisplayError(_("Position not found in file"), 0);
12418                 return FALSE;
12419             }
12420             if (fenMode || line[0] == '#') pn--;
12421         }
12422     }
12423
12424     if (fenMode) {
12425         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12426             DisplayError(_("Bad FEN position in file"), 0);
12427             return FALSE;
12428         }
12429     } else {
12430         (void) fgets(line, MSG_SIZ, f);
12431         (void) fgets(line, MSG_SIZ, f);
12432
12433         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12434             (void) fgets(line, MSG_SIZ, f);
12435             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12436                 if (*p == ' ')
12437                   continue;
12438                 initial_position[i][j++] = CharToPiece(*p);
12439             }
12440         }
12441
12442         blackPlaysFirst = FALSE;
12443         if (!feof(f)) {
12444             (void) fgets(line, MSG_SIZ, f);
12445             if (strncmp(line, "black", strlen("black"))==0)
12446               blackPlaysFirst = TRUE;
12447         }
12448     }
12449     startedFromSetupPosition = TRUE;
12450
12451     CopyBoard(boards[0], initial_position);
12452     if (blackPlaysFirst) {
12453         currentMove = forwardMostMove = backwardMostMove = 1;
12454         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12455         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12456         CopyBoard(boards[1], initial_position);
12457         DisplayMessage("", _("Black to play"));
12458     } else {
12459         currentMove = forwardMostMove = backwardMostMove = 0;
12460         DisplayMessage("", _("White to play"));
12461     }
12462     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12463     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12464         SendToProgram("force\n", &first);
12465         SendBoard(&first, forwardMostMove);
12466     }
12467     if (appData.debugMode) {
12468 int i, j;
12469   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12470   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12471         fprintf(debugFP, "Load Position\n");
12472     }
12473
12474     if (positionNumber > 1) {
12475       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12476         DisplayTitle(line);
12477     } else {
12478         DisplayTitle(title);
12479     }
12480     gameMode = EditGame;
12481     ModeHighlight();
12482     ResetClocks();
12483     timeRemaining[0][1] = whiteTimeRemaining;
12484     timeRemaining[1][1] = blackTimeRemaining;
12485     DrawPosition(FALSE, boards[currentMove]);
12486
12487     return TRUE;
12488 }
12489
12490
12491 void
12492 CopyPlayerNameIntoFileName (char **dest, char *src)
12493 {
12494     while (*src != NULLCHAR && *src != ',') {
12495         if (*src == ' ') {
12496             *(*dest)++ = '_';
12497             src++;
12498         } else {
12499             *(*dest)++ = *src++;
12500         }
12501     }
12502 }
12503
12504 char *
12505 DefaultFileName (char *ext)
12506 {
12507     static char def[MSG_SIZ];
12508     char *p;
12509
12510     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12511         p = def;
12512         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12513         *p++ = '-';
12514         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12515         *p++ = '.';
12516         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12517     } else {
12518         def[0] = NULLCHAR;
12519     }
12520     return def;
12521 }
12522
12523 /* Save the current game to the given file */
12524 int
12525 SaveGameToFile (char *filename, int append)
12526 {
12527     FILE *f;
12528     char buf[MSG_SIZ];
12529     int result, i, t,tot=0;
12530
12531     if (strcmp(filename, "-") == 0) {
12532         return SaveGame(stdout, 0, NULL);
12533     } else {
12534         for(i=0; i<10; i++) { // upto 10 tries
12535              f = fopen(filename, append ? "a" : "w");
12536              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12537              if(f || errno != 13) break;
12538              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12539              tot += t;
12540         }
12541         if (f == NULL) {
12542             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12543             DisplayError(buf, errno);
12544             return FALSE;
12545         } else {
12546             safeStrCpy(buf, lastMsg, MSG_SIZ);
12547             DisplayMessage(_("Waiting for access to save file"), "");
12548             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12549             DisplayMessage(_("Saving game"), "");
12550             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12551             result = SaveGame(f, 0, NULL);
12552             DisplayMessage(buf, "");
12553             return result;
12554         }
12555     }
12556 }
12557
12558 char *
12559 SavePart (char *str)
12560 {
12561     static char buf[MSG_SIZ];
12562     char *p;
12563
12564     p = strchr(str, ' ');
12565     if (p == NULL) return str;
12566     strncpy(buf, str, p - str);
12567     buf[p - str] = NULLCHAR;
12568     return buf;
12569 }
12570
12571 #define PGN_MAX_LINE 75
12572
12573 #define PGN_SIDE_WHITE  0
12574 #define PGN_SIDE_BLACK  1
12575
12576 static int
12577 FindFirstMoveOutOfBook (int side)
12578 {
12579     int result = -1;
12580
12581     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12582         int index = backwardMostMove;
12583         int has_book_hit = 0;
12584
12585         if( (index % 2) != side ) {
12586             index++;
12587         }
12588
12589         while( index < forwardMostMove ) {
12590             /* Check to see if engine is in book */
12591             int depth = pvInfoList[index].depth;
12592             int score = pvInfoList[index].score;
12593             int in_book = 0;
12594
12595             if( depth <= 2 ) {
12596                 in_book = 1;
12597             }
12598             else if( score == 0 && depth == 63 ) {
12599                 in_book = 1; /* Zappa */
12600             }
12601             else if( score == 2 && depth == 99 ) {
12602                 in_book = 1; /* Abrok */
12603             }
12604
12605             has_book_hit += in_book;
12606
12607             if( ! in_book ) {
12608                 result = index;
12609
12610                 break;
12611             }
12612
12613             index += 2;
12614         }
12615     }
12616
12617     return result;
12618 }
12619
12620 void
12621 GetOutOfBookInfo (char * buf)
12622 {
12623     int oob[2];
12624     int i;
12625     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12626
12627     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12628     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12629
12630     *buf = '\0';
12631
12632     if( oob[0] >= 0 || oob[1] >= 0 ) {
12633         for( i=0; i<2; i++ ) {
12634             int idx = oob[i];
12635
12636             if( idx >= 0 ) {
12637                 if( i > 0 && oob[0] >= 0 ) {
12638                     strcat( buf, "   " );
12639                 }
12640
12641                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12642                 sprintf( buf+strlen(buf), "%s%.2f",
12643                     pvInfoList[idx].score >= 0 ? "+" : "",
12644                     pvInfoList[idx].score / 100.0 );
12645             }
12646         }
12647     }
12648 }
12649
12650 /* Save game in PGN style and close the file */
12651 int
12652 SaveGamePGN (FILE *f)
12653 {
12654     int i, offset, linelen, newblock;
12655 //    char *movetext;
12656     char numtext[32];
12657     int movelen, numlen, blank;
12658     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12659
12660     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12661
12662     PrintPGNTags(f, &gameInfo);
12663
12664     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12665
12666     if (backwardMostMove > 0 || startedFromSetupPosition) {
12667         char *fen = PositionToFEN(backwardMostMove, NULL);
12668         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12669         fprintf(f, "\n{--------------\n");
12670         PrintPosition(f, backwardMostMove);
12671         fprintf(f, "--------------}\n");
12672         free(fen);
12673     }
12674     else {
12675         /* [AS] Out of book annotation */
12676         if( appData.saveOutOfBookInfo ) {
12677             char buf[64];
12678
12679             GetOutOfBookInfo( buf );
12680
12681             if( buf[0] != '\0' ) {
12682                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12683             }
12684         }
12685
12686         fprintf(f, "\n");
12687     }
12688
12689     i = backwardMostMove;
12690     linelen = 0;
12691     newblock = TRUE;
12692
12693     while (i < forwardMostMove) {
12694         /* Print comments preceding this move */
12695         if (commentList[i] != NULL) {
12696             if (linelen > 0) fprintf(f, "\n");
12697             fprintf(f, "%s", commentList[i]);
12698             linelen = 0;
12699             newblock = TRUE;
12700         }
12701
12702         /* Format move number */
12703         if ((i % 2) == 0)
12704           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12705         else
12706           if (newblock)
12707             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12708           else
12709             numtext[0] = NULLCHAR;
12710
12711         numlen = strlen(numtext);
12712         newblock = FALSE;
12713
12714         /* Print move number */
12715         blank = linelen > 0 && numlen > 0;
12716         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12717             fprintf(f, "\n");
12718             linelen = 0;
12719             blank = 0;
12720         }
12721         if (blank) {
12722             fprintf(f, " ");
12723             linelen++;
12724         }
12725         fprintf(f, "%s", numtext);
12726         linelen += numlen;
12727
12728         /* Get move */
12729         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12730         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12731
12732         /* Print move */
12733         blank = linelen > 0 && movelen > 0;
12734         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12735             fprintf(f, "\n");
12736             linelen = 0;
12737             blank = 0;
12738         }
12739         if (blank) {
12740             fprintf(f, " ");
12741             linelen++;
12742         }
12743         fprintf(f, "%s", move_buffer);
12744         linelen += movelen;
12745
12746         /* [AS] Add PV info if present */
12747         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12748             /* [HGM] add time */
12749             char buf[MSG_SIZ]; int seconds;
12750
12751             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12752
12753             if( seconds <= 0)
12754               buf[0] = 0;
12755             else
12756               if( seconds < 30 )
12757                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12758               else
12759                 {
12760                   seconds = (seconds + 4)/10; // round to full seconds
12761                   if( seconds < 60 )
12762                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12763                   else
12764                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12765                 }
12766
12767             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12768                       pvInfoList[i].score >= 0 ? "+" : "",
12769                       pvInfoList[i].score / 100.0,
12770                       pvInfoList[i].depth,
12771                       buf );
12772
12773             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12774
12775             /* Print score/depth */
12776             blank = linelen > 0 && movelen > 0;
12777             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12778                 fprintf(f, "\n");
12779                 linelen = 0;
12780                 blank = 0;
12781             }
12782             if (blank) {
12783                 fprintf(f, " ");
12784                 linelen++;
12785             }
12786             fprintf(f, "%s", move_buffer);
12787             linelen += movelen;
12788         }
12789
12790         i++;
12791     }
12792
12793     /* Start a new line */
12794     if (linelen > 0) fprintf(f, "\n");
12795
12796     /* Print comments after last move */
12797     if (commentList[i] != NULL) {
12798         fprintf(f, "%s\n", commentList[i]);
12799     }
12800
12801     /* Print result */
12802     if (gameInfo.resultDetails != NULL &&
12803         gameInfo.resultDetails[0] != NULLCHAR) {
12804         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12805                 PGNResult(gameInfo.result));
12806     } else {
12807         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12808     }
12809
12810     fclose(f);
12811     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12812     return TRUE;
12813 }
12814
12815 /* Save game in old style and close the file */
12816 int
12817 SaveGameOldStyle (FILE *f)
12818 {
12819     int i, offset;
12820     time_t tm;
12821
12822     tm = time((time_t *) NULL);
12823
12824     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12825     PrintOpponents(f);
12826
12827     if (backwardMostMove > 0 || startedFromSetupPosition) {
12828         fprintf(f, "\n[--------------\n");
12829         PrintPosition(f, backwardMostMove);
12830         fprintf(f, "--------------]\n");
12831     } else {
12832         fprintf(f, "\n");
12833     }
12834
12835     i = backwardMostMove;
12836     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12837
12838     while (i < forwardMostMove) {
12839         if (commentList[i] != NULL) {
12840             fprintf(f, "[%s]\n", commentList[i]);
12841         }
12842
12843         if ((i % 2) == 1) {
12844             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12845             i++;
12846         } else {
12847             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12848             i++;
12849             if (commentList[i] != NULL) {
12850                 fprintf(f, "\n");
12851                 continue;
12852             }
12853             if (i >= forwardMostMove) {
12854                 fprintf(f, "\n");
12855                 break;
12856             }
12857             fprintf(f, "%s\n", parseList[i]);
12858             i++;
12859         }
12860     }
12861
12862     if (commentList[i] != NULL) {
12863         fprintf(f, "[%s]\n", commentList[i]);
12864     }
12865
12866     /* This isn't really the old style, but it's close enough */
12867     if (gameInfo.resultDetails != NULL &&
12868         gameInfo.resultDetails[0] != NULLCHAR) {
12869         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12870                 gameInfo.resultDetails);
12871     } else {
12872         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12873     }
12874
12875     fclose(f);
12876     return TRUE;
12877 }
12878
12879 /* Save the current game to open file f and close the file */
12880 int
12881 SaveGame (FILE *f, int dummy, char *dummy2)
12882 {
12883     if (gameMode == EditPosition) EditPositionDone(TRUE);
12884     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12885     if (appData.oldSaveStyle)
12886       return SaveGameOldStyle(f);
12887     else
12888       return SaveGamePGN(f);
12889 }
12890
12891 /* Save the current position to the given file */
12892 int
12893 SavePositionToFile (char *filename)
12894 {
12895     FILE *f;
12896     char buf[MSG_SIZ];
12897
12898     if (strcmp(filename, "-") == 0) {
12899         return SavePosition(stdout, 0, NULL);
12900     } else {
12901         f = fopen(filename, "a");
12902         if (f == NULL) {
12903             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12904             DisplayError(buf, errno);
12905             return FALSE;
12906         } else {
12907             safeStrCpy(buf, lastMsg, MSG_SIZ);
12908             DisplayMessage(_("Waiting for access to save file"), "");
12909             flock(fileno(f), LOCK_EX); // [HGM] lock
12910             DisplayMessage(_("Saving position"), "");
12911             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12912             SavePosition(f, 0, NULL);
12913             DisplayMessage(buf, "");
12914             return TRUE;
12915         }
12916     }
12917 }
12918
12919 /* Save the current position to the given open file and close the file */
12920 int
12921 SavePosition (FILE *f, int dummy, char *dummy2)
12922 {
12923     time_t tm;
12924     char *fen;
12925
12926     if (gameMode == EditPosition) EditPositionDone(TRUE);
12927     if (appData.oldSaveStyle) {
12928         tm = time((time_t *) NULL);
12929
12930         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12931         PrintOpponents(f);
12932         fprintf(f, "[--------------\n");
12933         PrintPosition(f, currentMove);
12934         fprintf(f, "--------------]\n");
12935     } else {
12936         fen = PositionToFEN(currentMove, NULL);
12937         fprintf(f, "%s\n", fen);
12938         free(fen);
12939     }
12940     fclose(f);
12941     return TRUE;
12942 }
12943
12944 void
12945 ReloadCmailMsgEvent (int unregister)
12946 {
12947 #if !WIN32
12948     static char *inFilename = NULL;
12949     static char *outFilename;
12950     int i;
12951     struct stat inbuf, outbuf;
12952     int status;
12953
12954     /* Any registered moves are unregistered if unregister is set, */
12955     /* i.e. invoked by the signal handler */
12956     if (unregister) {
12957         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12958             cmailMoveRegistered[i] = FALSE;
12959             if (cmailCommentList[i] != NULL) {
12960                 free(cmailCommentList[i]);
12961                 cmailCommentList[i] = NULL;
12962             }
12963         }
12964         nCmailMovesRegistered = 0;
12965     }
12966
12967     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12968         cmailResult[i] = CMAIL_NOT_RESULT;
12969     }
12970     nCmailResults = 0;
12971
12972     if (inFilename == NULL) {
12973         /* Because the filenames are static they only get malloced once  */
12974         /* and they never get freed                                      */
12975         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12976         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12977
12978         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12979         sprintf(outFilename, "%s.out", appData.cmailGameName);
12980     }
12981
12982     status = stat(outFilename, &outbuf);
12983     if (status < 0) {
12984         cmailMailedMove = FALSE;
12985     } else {
12986         status = stat(inFilename, &inbuf);
12987         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12988     }
12989
12990     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12991        counts the games, notes how each one terminated, etc.
12992
12993        It would be nice to remove this kludge and instead gather all
12994        the information while building the game list.  (And to keep it
12995        in the game list nodes instead of having a bunch of fixed-size
12996        parallel arrays.)  Note this will require getting each game's
12997        termination from the PGN tags, as the game list builder does
12998        not process the game moves.  --mann
12999        */
13000     cmailMsgLoaded = TRUE;
13001     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13002
13003     /* Load first game in the file or popup game menu */
13004     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13005
13006 #endif /* !WIN32 */
13007     return;
13008 }
13009
13010 int
13011 RegisterMove ()
13012 {
13013     FILE *f;
13014     char string[MSG_SIZ];
13015
13016     if (   cmailMailedMove
13017         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13018         return TRUE;            /* Allow free viewing  */
13019     }
13020
13021     /* Unregister move to ensure that we don't leave RegisterMove        */
13022     /* with the move registered when the conditions for registering no   */
13023     /* longer hold                                                       */
13024     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13025         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13026         nCmailMovesRegistered --;
13027
13028         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13029           {
13030               free(cmailCommentList[lastLoadGameNumber - 1]);
13031               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13032           }
13033     }
13034
13035     if (cmailOldMove == -1) {
13036         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13037         return FALSE;
13038     }
13039
13040     if (currentMove > cmailOldMove + 1) {
13041         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13042         return FALSE;
13043     }
13044
13045     if (currentMove < cmailOldMove) {
13046         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13047         return FALSE;
13048     }
13049
13050     if (forwardMostMove > currentMove) {
13051         /* Silently truncate extra moves */
13052         TruncateGame();
13053     }
13054
13055     if (   (currentMove == cmailOldMove + 1)
13056         || (   (currentMove == cmailOldMove)
13057             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13058                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13059         if (gameInfo.result != GameUnfinished) {
13060             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13061         }
13062
13063         if (commentList[currentMove] != NULL) {
13064             cmailCommentList[lastLoadGameNumber - 1]
13065               = StrSave(commentList[currentMove]);
13066         }
13067         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13068
13069         if (appData.debugMode)
13070           fprintf(debugFP, "Saving %s for game %d\n",
13071                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13072
13073         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13074
13075         f = fopen(string, "w");
13076         if (appData.oldSaveStyle) {
13077             SaveGameOldStyle(f); /* also closes the file */
13078
13079             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13080             f = fopen(string, "w");
13081             SavePosition(f, 0, NULL); /* also closes the file */
13082         } else {
13083             fprintf(f, "{--------------\n");
13084             PrintPosition(f, currentMove);
13085             fprintf(f, "--------------}\n\n");
13086
13087             SaveGame(f, 0, NULL); /* also closes the file*/
13088         }
13089
13090         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13091         nCmailMovesRegistered ++;
13092     } else if (nCmailGames == 1) {
13093         DisplayError(_("You have not made a move yet"), 0);
13094         return FALSE;
13095     }
13096
13097     return TRUE;
13098 }
13099
13100 void
13101 MailMoveEvent ()
13102 {
13103 #if !WIN32
13104     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13105     FILE *commandOutput;
13106     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13107     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13108     int nBuffers;
13109     int i;
13110     int archived;
13111     char *arcDir;
13112
13113     if (! cmailMsgLoaded) {
13114         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13115         return;
13116     }
13117
13118     if (nCmailGames == nCmailResults) {
13119         DisplayError(_("No unfinished games"), 0);
13120         return;
13121     }
13122
13123 #if CMAIL_PROHIBIT_REMAIL
13124     if (cmailMailedMove) {
13125       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);
13126         DisplayError(msg, 0);
13127         return;
13128     }
13129 #endif
13130
13131     if (! (cmailMailedMove || RegisterMove())) return;
13132
13133     if (   cmailMailedMove
13134         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13135       snprintf(string, MSG_SIZ, partCommandString,
13136                appData.debugMode ? " -v" : "", appData.cmailGameName);
13137         commandOutput = popen(string, "r");
13138
13139         if (commandOutput == NULL) {
13140             DisplayError(_("Failed to invoke cmail"), 0);
13141         } else {
13142             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13143                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13144             }
13145             if (nBuffers > 1) {
13146                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13147                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13148                 nBytes = MSG_SIZ - 1;
13149             } else {
13150                 (void) memcpy(msg, buffer, nBytes);
13151             }
13152             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13153
13154             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13155                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13156
13157                 archived = TRUE;
13158                 for (i = 0; i < nCmailGames; i ++) {
13159                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13160                         archived = FALSE;
13161                     }
13162                 }
13163                 if (   archived
13164                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13165                         != NULL)) {
13166                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13167                            arcDir,
13168                            appData.cmailGameName,
13169                            gameInfo.date);
13170                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13171                     cmailMsgLoaded = FALSE;
13172                 }
13173             }
13174
13175             DisplayInformation(msg);
13176             pclose(commandOutput);
13177         }
13178     } else {
13179         if ((*cmailMsg) != '\0') {
13180             DisplayInformation(cmailMsg);
13181         }
13182     }
13183
13184     return;
13185 #endif /* !WIN32 */
13186 }
13187
13188 char *
13189 CmailMsg ()
13190 {
13191 #if WIN32
13192     return NULL;
13193 #else
13194     int  prependComma = 0;
13195     char number[5];
13196     char string[MSG_SIZ];       /* Space for game-list */
13197     int  i;
13198
13199     if (!cmailMsgLoaded) return "";
13200
13201     if (cmailMailedMove) {
13202       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13203     } else {
13204         /* Create a list of games left */
13205       snprintf(string, MSG_SIZ, "[");
13206         for (i = 0; i < nCmailGames; i ++) {
13207             if (! (   cmailMoveRegistered[i]
13208                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13209                 if (prependComma) {
13210                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13211                 } else {
13212                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13213                     prependComma = 1;
13214                 }
13215
13216                 strcat(string, number);
13217             }
13218         }
13219         strcat(string, "]");
13220
13221         if (nCmailMovesRegistered + nCmailResults == 0) {
13222             switch (nCmailGames) {
13223               case 1:
13224                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13225                 break;
13226
13227               case 2:
13228                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13229                 break;
13230
13231               default:
13232                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13233                          nCmailGames);
13234                 break;
13235             }
13236         } else {
13237             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13238               case 1:
13239                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13240                          string);
13241                 break;
13242
13243               case 0:
13244                 if (nCmailResults == nCmailGames) {
13245                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13246                 } else {
13247                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13248                 }
13249                 break;
13250
13251               default:
13252                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13253                          string);
13254             }
13255         }
13256     }
13257     return cmailMsg;
13258 #endif /* WIN32 */
13259 }
13260
13261 void
13262 ResetGameEvent ()
13263 {
13264     if (gameMode == Training)
13265       SetTrainingModeOff();
13266
13267     Reset(TRUE, TRUE);
13268     cmailMsgLoaded = FALSE;
13269     if (appData.icsActive) {
13270       SendToICS(ics_prefix);
13271       SendToICS("refresh\n");
13272     }
13273 }
13274
13275 void
13276 ExitEvent (int status)
13277 {
13278     exiting++;
13279     if (exiting > 2) {
13280       /* Give up on clean exit */
13281       exit(status);
13282     }
13283     if (exiting > 1) {
13284       /* Keep trying for clean exit */
13285       return;
13286     }
13287
13288     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13289
13290     if (telnetISR != NULL) {
13291       RemoveInputSource(telnetISR);
13292     }
13293     if (icsPR != NoProc) {
13294       DestroyChildProcess(icsPR, TRUE);
13295     }
13296
13297     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13298     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13299
13300     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13301     /* make sure this other one finishes before killing it!                  */
13302     if(endingGame) { int count = 0;
13303         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13304         while(endingGame && count++ < 10) DoSleep(1);
13305         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13306     }
13307
13308     /* Kill off chess programs */
13309     if (first.pr != NoProc) {
13310         ExitAnalyzeMode();
13311
13312         DoSleep( appData.delayBeforeQuit );
13313         SendToProgram("quit\n", &first);
13314         DoSleep( appData.delayAfterQuit );
13315         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13316     }
13317     if (second.pr != NoProc) {
13318         DoSleep( appData.delayBeforeQuit );
13319         SendToProgram("quit\n", &second);
13320         DoSleep( appData.delayAfterQuit );
13321         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13322     }
13323     if (first.isr != NULL) {
13324         RemoveInputSource(first.isr);
13325     }
13326     if (second.isr != NULL) {
13327         RemoveInputSource(second.isr);
13328     }
13329
13330     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13331     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13332
13333     ShutDownFrontEnd();
13334     exit(status);
13335 }
13336
13337 void
13338 PauseEvent ()
13339 {
13340     if (appData.debugMode)
13341         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13342     if (pausing) {
13343         pausing = FALSE;
13344         ModeHighlight();
13345         if (gameMode == MachinePlaysWhite ||
13346             gameMode == MachinePlaysBlack) {
13347             StartClocks();
13348         } else {
13349             DisplayBothClocks();
13350         }
13351         if (gameMode == PlayFromGameFile) {
13352             if (appData.timeDelay >= 0)
13353                 AutoPlayGameLoop();
13354         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13355             Reset(FALSE, TRUE);
13356             SendToICS(ics_prefix);
13357             SendToICS("refresh\n");
13358         } else if (currentMove < forwardMostMove) {
13359             ForwardInner(forwardMostMove);
13360         }
13361         pauseExamInvalid = FALSE;
13362     } else {
13363         switch (gameMode) {
13364           default:
13365             return;
13366           case IcsExamining:
13367             pauseExamForwardMostMove = forwardMostMove;
13368             pauseExamInvalid = FALSE;
13369             /* fall through */
13370           case IcsObserving:
13371           case IcsPlayingWhite:
13372           case IcsPlayingBlack:
13373             pausing = TRUE;
13374             ModeHighlight();
13375             return;
13376           case PlayFromGameFile:
13377             (void) StopLoadGameTimer();
13378             pausing = TRUE;
13379             ModeHighlight();
13380             break;
13381           case BeginningOfGame:
13382             if (appData.icsActive) return;
13383             /* else fall through */
13384           case MachinePlaysWhite:
13385           case MachinePlaysBlack:
13386           case TwoMachinesPlay:
13387             if (forwardMostMove == 0)
13388               return;           /* don't pause if no one has moved */
13389             if ((gameMode == MachinePlaysWhite &&
13390                  !WhiteOnMove(forwardMostMove)) ||
13391                 (gameMode == MachinePlaysBlack &&
13392                  WhiteOnMove(forwardMostMove))) {
13393                 StopClocks();
13394             }
13395           case AnalyzeMode:
13396             pausing = TRUE;
13397             ModeHighlight();
13398             break;
13399         }
13400     }
13401 }
13402
13403 void
13404 EditCommentEvent ()
13405 {
13406     char title[MSG_SIZ];
13407
13408     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13409       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13410     } else {
13411       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13412                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13413                parseList[currentMove - 1]);
13414     }
13415
13416     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13417 }
13418
13419
13420 void
13421 EditTagsEvent ()
13422 {
13423     char *tags = PGNTags(&gameInfo);
13424     bookUp = FALSE;
13425     EditTagsPopUp(tags, NULL);
13426     free(tags);
13427 }
13428
13429 void
13430 ToggleSecond ()
13431 {
13432   if(second.analyzing) {
13433     SendToProgram("exit\n", &second);
13434     second.analyzing = FALSE;
13435   } else {
13436     if (second.pr == NoProc) StartChessProgram(&second);
13437     InitChessProgram(&second, FALSE);
13438     FeedMovesToProgram(&second, currentMove);
13439
13440     SendToProgram("analyze\n", &second);
13441     second.analyzing = TRUE;
13442   }
13443 }
13444
13445 void
13446 AnalyzeModeEvent ()
13447 {
13448     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13449     if (appData.noChessProgram || gameMode == AnalyzeMode)
13450       return;
13451
13452     if (gameMode != AnalyzeFile) {
13453         if (!appData.icsEngineAnalyze) {
13454                EditGameEvent();
13455                if (gameMode != EditGame) return;
13456         }
13457         ResurrectChessProgram();
13458         SendToProgram("analyze\n", &first);
13459         first.analyzing = TRUE;
13460         /*first.maybeThinking = TRUE;*/
13461         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13462         EngineOutputPopUp();
13463     }
13464     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13465     pausing = FALSE;
13466     ModeHighlight();
13467     SetGameInfo();
13468
13469     StartAnalysisClock();
13470     GetTimeMark(&lastNodeCountTime);
13471     lastNodeCount = 0;
13472 }
13473
13474 void
13475 AnalyzeFileEvent ()
13476 {
13477     if (appData.noChessProgram || gameMode == AnalyzeFile)
13478       return;
13479
13480     if (gameMode != AnalyzeMode) {
13481         EditGameEvent();
13482         if (gameMode != EditGame) return;
13483         ResurrectChessProgram();
13484         SendToProgram("analyze\n", &first);
13485         first.analyzing = TRUE;
13486         /*first.maybeThinking = TRUE;*/
13487         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13488         EngineOutputPopUp();
13489     }
13490     gameMode = AnalyzeFile;
13491     pausing = FALSE;
13492     ModeHighlight();
13493     SetGameInfo();
13494
13495     StartAnalysisClock();
13496     GetTimeMark(&lastNodeCountTime);
13497     lastNodeCount = 0;
13498     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13499 }
13500
13501 void
13502 MachineWhiteEvent ()
13503 {
13504     char buf[MSG_SIZ];
13505     char *bookHit = NULL;
13506
13507     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13508       return;
13509
13510
13511     if (gameMode == PlayFromGameFile ||
13512         gameMode == TwoMachinesPlay  ||
13513         gameMode == Training         ||
13514         gameMode == AnalyzeMode      ||
13515         gameMode == EndOfGame)
13516         EditGameEvent();
13517
13518     if (gameMode == EditPosition)
13519         EditPositionDone(TRUE);
13520
13521     if (!WhiteOnMove(currentMove)) {
13522         DisplayError(_("It is not White's turn"), 0);
13523         return;
13524     }
13525
13526     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13527       ExitAnalyzeMode();
13528
13529     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13530         gameMode == AnalyzeFile)
13531         TruncateGame();
13532
13533     ResurrectChessProgram();    /* in case it isn't running */
13534     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13535         gameMode = MachinePlaysWhite;
13536         ResetClocks();
13537     } else
13538     gameMode = MachinePlaysWhite;
13539     pausing = FALSE;
13540     ModeHighlight();
13541     SetGameInfo();
13542     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13543     DisplayTitle(buf);
13544     if (first.sendName) {
13545       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13546       SendToProgram(buf, &first);
13547     }
13548     if (first.sendTime) {
13549       if (first.useColors) {
13550         SendToProgram("black\n", &first); /*gnu kludge*/
13551       }
13552       SendTimeRemaining(&first, TRUE);
13553     }
13554     if (first.useColors) {
13555       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13556     }
13557     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13558     SetMachineThinkingEnables();
13559     first.maybeThinking = TRUE;
13560     StartClocks();
13561     firstMove = FALSE;
13562
13563     if (appData.autoFlipView && !flipView) {
13564       flipView = !flipView;
13565       DrawPosition(FALSE, NULL);
13566       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13567     }
13568
13569     if(bookHit) { // [HGM] book: simulate book reply
13570         static char bookMove[MSG_SIZ]; // a bit generous?
13571
13572         programStats.nodes = programStats.depth = programStats.time =
13573         programStats.score = programStats.got_only_move = 0;
13574         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13575
13576         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13577         strcat(bookMove, bookHit);
13578         HandleMachineMove(bookMove, &first);
13579     }
13580 }
13581
13582 void
13583 MachineBlackEvent ()
13584 {
13585   char buf[MSG_SIZ];
13586   char *bookHit = NULL;
13587
13588     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13589         return;
13590
13591
13592     if (gameMode == PlayFromGameFile ||
13593         gameMode == TwoMachinesPlay  ||
13594         gameMode == Training         ||
13595         gameMode == AnalyzeMode      ||
13596         gameMode == EndOfGame)
13597         EditGameEvent();
13598
13599     if (gameMode == EditPosition)
13600         EditPositionDone(TRUE);
13601
13602     if (WhiteOnMove(currentMove)) {
13603         DisplayError(_("It is not Black's turn"), 0);
13604         return;
13605     }
13606
13607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13608       ExitAnalyzeMode();
13609
13610     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13611         gameMode == AnalyzeFile)
13612         TruncateGame();
13613
13614     ResurrectChessProgram();    /* in case it isn't running */
13615     gameMode = MachinePlaysBlack;
13616     pausing = FALSE;
13617     ModeHighlight();
13618     SetGameInfo();
13619     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13620     DisplayTitle(buf);
13621     if (first.sendName) {
13622       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13623       SendToProgram(buf, &first);
13624     }
13625     if (first.sendTime) {
13626       if (first.useColors) {
13627         SendToProgram("white\n", &first); /*gnu kludge*/
13628       }
13629       SendTimeRemaining(&first, FALSE);
13630     }
13631     if (first.useColors) {
13632       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13633     }
13634     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13635     SetMachineThinkingEnables();
13636     first.maybeThinking = TRUE;
13637     StartClocks();
13638
13639     if (appData.autoFlipView && flipView) {
13640       flipView = !flipView;
13641       DrawPosition(FALSE, NULL);
13642       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13643     }
13644     if(bookHit) { // [HGM] book: simulate book reply
13645         static char bookMove[MSG_SIZ]; // a bit generous?
13646
13647         programStats.nodes = programStats.depth = programStats.time =
13648         programStats.score = programStats.got_only_move = 0;
13649         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13650
13651         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13652         strcat(bookMove, bookHit);
13653         HandleMachineMove(bookMove, &first);
13654     }
13655 }
13656
13657
13658 void
13659 DisplayTwoMachinesTitle ()
13660 {
13661     char buf[MSG_SIZ];
13662     if (appData.matchGames > 0) {
13663         if(appData.tourneyFile[0]) {
13664           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13665                    gameInfo.white, _("vs."), gameInfo.black,
13666                    nextGame+1, appData.matchGames+1,
13667                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13668         } else 
13669         if (first.twoMachinesColor[0] == 'w') {
13670           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13671                    gameInfo.white, _("vs."),  gameInfo.black,
13672                    first.matchWins, second.matchWins,
13673                    matchGame - 1 - (first.matchWins + second.matchWins));
13674         } else {
13675           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13676                    gameInfo.white, _("vs."), gameInfo.black,
13677                    second.matchWins, first.matchWins,
13678                    matchGame - 1 - (first.matchWins + second.matchWins));
13679         }
13680     } else {
13681       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13682     }
13683     DisplayTitle(buf);
13684 }
13685
13686 void
13687 SettingsMenuIfReady ()
13688 {
13689   if (second.lastPing != second.lastPong) {
13690     DisplayMessage("", _("Waiting for second chess program"));
13691     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13692     return;
13693   }
13694   ThawUI();
13695   DisplayMessage("", "");
13696   SettingsPopUp(&second);
13697 }
13698
13699 int
13700 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13701 {
13702     char buf[MSG_SIZ];
13703     if (cps->pr == NoProc) {
13704         StartChessProgram(cps);
13705         if (cps->protocolVersion == 1) {
13706           retry();
13707         } else {
13708           /* kludge: allow timeout for initial "feature" command */
13709           FreezeUI();
13710           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13711           DisplayMessage("", buf);
13712           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13713         }
13714         return 1;
13715     }
13716     return 0;
13717 }
13718
13719 void
13720 TwoMachinesEvent P((void))
13721 {
13722     int i;
13723     char buf[MSG_SIZ];
13724     ChessProgramState *onmove;
13725     char *bookHit = NULL;
13726     static int stalling = 0;
13727     TimeMark now;
13728     long wait;
13729
13730     if (appData.noChessProgram) return;
13731
13732     switch (gameMode) {
13733       case TwoMachinesPlay:
13734         return;
13735       case MachinePlaysWhite:
13736       case MachinePlaysBlack:
13737         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13738             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13739             return;
13740         }
13741         /* fall through */
13742       case BeginningOfGame:
13743       case PlayFromGameFile:
13744       case EndOfGame:
13745         EditGameEvent();
13746         if (gameMode != EditGame) return;
13747         break;
13748       case EditPosition:
13749         EditPositionDone(TRUE);
13750         break;
13751       case AnalyzeMode:
13752       case AnalyzeFile:
13753         ExitAnalyzeMode();
13754         break;
13755       case EditGame:
13756       default:
13757         break;
13758     }
13759
13760 //    forwardMostMove = currentMove;
13761     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13762
13763     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13764
13765     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13766     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13767       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13768       return;
13769     }
13770
13771     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13772         DisplayError("second engine does not play this", 0);
13773         return;
13774     }
13775
13776     if(!stalling) {
13777       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13778       SendToProgram("force\n", &second);
13779       stalling = 1;
13780       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13781       return;
13782     }
13783     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13784     if(appData.matchPause>10000 || appData.matchPause<10)
13785                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13786     wait = SubtractTimeMarks(&now, &pauseStart);
13787     if(wait < appData.matchPause) {
13788         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13789         return;
13790     }
13791     // we are now committed to starting the game
13792     stalling = 0;
13793     DisplayMessage("", "");
13794     if (startedFromSetupPosition) {
13795         SendBoard(&second, backwardMostMove);
13796     if (appData.debugMode) {
13797         fprintf(debugFP, "Two Machines\n");
13798     }
13799     }
13800     for (i = backwardMostMove; i < forwardMostMove; i++) {
13801         SendMoveToProgram(i, &second);
13802     }
13803
13804     gameMode = TwoMachinesPlay;
13805     pausing = FALSE;
13806     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13807     SetGameInfo();
13808     DisplayTwoMachinesTitle();
13809     firstMove = TRUE;
13810     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13811         onmove = &first;
13812     } else {
13813         onmove = &second;
13814     }
13815     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13816     SendToProgram(first.computerString, &first);
13817     if (first.sendName) {
13818       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13819       SendToProgram(buf, &first);
13820     }
13821     SendToProgram(second.computerString, &second);
13822     if (second.sendName) {
13823       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13824       SendToProgram(buf, &second);
13825     }
13826
13827     ResetClocks();
13828     if (!first.sendTime || !second.sendTime) {
13829         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13830         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13831     }
13832     if (onmove->sendTime) {
13833       if (onmove->useColors) {
13834         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13835       }
13836       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13837     }
13838     if (onmove->useColors) {
13839       SendToProgram(onmove->twoMachinesColor, onmove);
13840     }
13841     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13842 //    SendToProgram("go\n", onmove);
13843     onmove->maybeThinking = TRUE;
13844     SetMachineThinkingEnables();
13845
13846     StartClocks();
13847
13848     if(bookHit) { // [HGM] book: simulate book reply
13849         static char bookMove[MSG_SIZ]; // a bit generous?
13850
13851         programStats.nodes = programStats.depth = programStats.time =
13852         programStats.score = programStats.got_only_move = 0;
13853         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13854
13855         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13856         strcat(bookMove, bookHit);
13857         savedMessage = bookMove; // args for deferred call
13858         savedState = onmove;
13859         ScheduleDelayedEvent(DeferredBookMove, 1);
13860     }
13861 }
13862
13863 void
13864 TrainingEvent ()
13865 {
13866     if (gameMode == Training) {
13867       SetTrainingModeOff();
13868       gameMode = PlayFromGameFile;
13869       DisplayMessage("", _("Training mode off"));
13870     } else {
13871       gameMode = Training;
13872       animateTraining = appData.animate;
13873
13874       /* make sure we are not already at the end of the game */
13875       if (currentMove < forwardMostMove) {
13876         SetTrainingModeOn();
13877         DisplayMessage("", _("Training mode on"));
13878       } else {
13879         gameMode = PlayFromGameFile;
13880         DisplayError(_("Already at end of game"), 0);
13881       }
13882     }
13883     ModeHighlight();
13884 }
13885
13886 void
13887 IcsClientEvent ()
13888 {
13889     if (!appData.icsActive) return;
13890     switch (gameMode) {
13891       case IcsPlayingWhite:
13892       case IcsPlayingBlack:
13893       case IcsObserving:
13894       case IcsIdle:
13895       case BeginningOfGame:
13896       case IcsExamining:
13897         return;
13898
13899       case EditGame:
13900         break;
13901
13902       case EditPosition:
13903         EditPositionDone(TRUE);
13904         break;
13905
13906       case AnalyzeMode:
13907       case AnalyzeFile:
13908         ExitAnalyzeMode();
13909         break;
13910
13911       default:
13912         EditGameEvent();
13913         break;
13914     }
13915
13916     gameMode = IcsIdle;
13917     ModeHighlight();
13918     return;
13919 }
13920
13921 void
13922 EditGameEvent ()
13923 {
13924     int i;
13925
13926     switch (gameMode) {
13927       case Training:
13928         SetTrainingModeOff();
13929         break;
13930       case MachinePlaysWhite:
13931       case MachinePlaysBlack:
13932       case BeginningOfGame:
13933         SendToProgram("force\n", &first);
13934         SetUserThinkingEnables();
13935         break;
13936       case PlayFromGameFile:
13937         (void) StopLoadGameTimer();
13938         if (gameFileFP != NULL) {
13939             gameFileFP = NULL;
13940         }
13941         break;
13942       case EditPosition:
13943         EditPositionDone(TRUE);
13944         break;
13945       case AnalyzeMode:
13946       case AnalyzeFile:
13947         ExitAnalyzeMode();
13948         SendToProgram("force\n", &first);
13949         break;
13950       case TwoMachinesPlay:
13951         GameEnds(EndOfFile, NULL, GE_PLAYER);
13952         ResurrectChessProgram();
13953         SetUserThinkingEnables();
13954         break;
13955       case EndOfGame:
13956         ResurrectChessProgram();
13957         break;
13958       case IcsPlayingBlack:
13959       case IcsPlayingWhite:
13960         DisplayError(_("Warning: You are still playing a game"), 0);
13961         break;
13962       case IcsObserving:
13963         DisplayError(_("Warning: You are still observing a game"), 0);
13964         break;
13965       case IcsExamining:
13966         DisplayError(_("Warning: You are still examining a game"), 0);
13967         break;
13968       case IcsIdle:
13969         break;
13970       case EditGame:
13971       default:
13972         return;
13973     }
13974
13975     pausing = FALSE;
13976     StopClocks();
13977     first.offeredDraw = second.offeredDraw = 0;
13978
13979     if (gameMode == PlayFromGameFile) {
13980         whiteTimeRemaining = timeRemaining[0][currentMove];
13981         blackTimeRemaining = timeRemaining[1][currentMove];
13982         DisplayTitle("");
13983     }
13984
13985     if (gameMode == MachinePlaysWhite ||
13986         gameMode == MachinePlaysBlack ||
13987         gameMode == TwoMachinesPlay ||
13988         gameMode == EndOfGame) {
13989         i = forwardMostMove;
13990         while (i > currentMove) {
13991             SendToProgram("undo\n", &first);
13992             i--;
13993         }
13994         if(!adjustedClock) {
13995         whiteTimeRemaining = timeRemaining[0][currentMove];
13996         blackTimeRemaining = timeRemaining[1][currentMove];
13997         DisplayBothClocks();
13998         }
13999         if (whiteFlag || blackFlag) {
14000             whiteFlag = blackFlag = 0;
14001         }
14002         DisplayTitle("");
14003     }
14004
14005     gameMode = EditGame;
14006     ModeHighlight();
14007     SetGameInfo();
14008 }
14009
14010
14011 void
14012 EditPositionEvent ()
14013 {
14014     if (gameMode == EditPosition) {
14015         EditGameEvent();
14016         return;
14017     }
14018
14019     EditGameEvent();
14020     if (gameMode != EditGame) return;
14021
14022     gameMode = EditPosition;
14023     ModeHighlight();
14024     SetGameInfo();
14025     if (currentMove > 0)
14026       CopyBoard(boards[0], boards[currentMove]);
14027
14028     blackPlaysFirst = !WhiteOnMove(currentMove);
14029     ResetClocks();
14030     currentMove = forwardMostMove = backwardMostMove = 0;
14031     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14032     DisplayMove(-1);
14033     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14034 }
14035
14036 void
14037 ExitAnalyzeMode ()
14038 {
14039     /* [DM] icsEngineAnalyze - possible call from other functions */
14040     if (appData.icsEngineAnalyze) {
14041         appData.icsEngineAnalyze = FALSE;
14042
14043         DisplayMessage("",_("Close ICS engine analyze..."));
14044     }
14045     if (first.analysisSupport && first.analyzing) {
14046       SendToBoth("exit\n");
14047       first.analyzing = second.analyzing = FALSE;
14048     }
14049     thinkOutput[0] = NULLCHAR;
14050 }
14051
14052 void
14053 EditPositionDone (Boolean fakeRights)
14054 {
14055     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14056
14057     startedFromSetupPosition = TRUE;
14058     InitChessProgram(&first, FALSE);
14059     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14060       boards[0][EP_STATUS] = EP_NONE;
14061       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14062       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14063         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14064         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14065       } else boards[0][CASTLING][2] = NoRights;
14066       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14067         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14068         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14069       } else boards[0][CASTLING][5] = NoRights;
14070       if(gameInfo.variant == VariantSChess) {
14071         int i;
14072         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14073           boards[0][VIRGIN][i] = 0;
14074           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14075           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14076         }
14077       }
14078     }
14079     SendToProgram("force\n", &first);
14080     if (blackPlaysFirst) {
14081         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14082         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14083         currentMove = forwardMostMove = backwardMostMove = 1;
14084         CopyBoard(boards[1], boards[0]);
14085     } else {
14086         currentMove = forwardMostMove = backwardMostMove = 0;
14087     }
14088     SendBoard(&first, forwardMostMove);
14089     if (appData.debugMode) {
14090         fprintf(debugFP, "EditPosDone\n");
14091     }
14092     DisplayTitle("");
14093     DisplayMessage("", "");
14094     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14095     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14096     gameMode = EditGame;
14097     ModeHighlight();
14098     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14099     ClearHighlights(); /* [AS] */
14100 }
14101
14102 /* Pause for `ms' milliseconds */
14103 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14104 void
14105 TimeDelay (long ms)
14106 {
14107     TimeMark m1, m2;
14108
14109     GetTimeMark(&m1);
14110     do {
14111         GetTimeMark(&m2);
14112     } while (SubtractTimeMarks(&m2, &m1) < ms);
14113 }
14114
14115 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14116 void
14117 SendMultiLineToICS (char *buf)
14118 {
14119     char temp[MSG_SIZ+1], *p;
14120     int len;
14121
14122     len = strlen(buf);
14123     if (len > MSG_SIZ)
14124       len = MSG_SIZ;
14125
14126     strncpy(temp, buf, len);
14127     temp[len] = 0;
14128
14129     p = temp;
14130     while (*p) {
14131         if (*p == '\n' || *p == '\r')
14132           *p = ' ';
14133         ++p;
14134     }
14135
14136     strcat(temp, "\n");
14137     SendToICS(temp);
14138     SendToPlayer(temp, strlen(temp));
14139 }
14140
14141 void
14142 SetWhiteToPlayEvent ()
14143 {
14144     if (gameMode == EditPosition) {
14145         blackPlaysFirst = FALSE;
14146         DisplayBothClocks();    /* works because currentMove is 0 */
14147     } else if (gameMode == IcsExamining) {
14148         SendToICS(ics_prefix);
14149         SendToICS("tomove white\n");
14150     }
14151 }
14152
14153 void
14154 SetBlackToPlayEvent ()
14155 {
14156     if (gameMode == EditPosition) {
14157         blackPlaysFirst = TRUE;
14158         currentMove = 1;        /* kludge */
14159         DisplayBothClocks();
14160         currentMove = 0;
14161     } else if (gameMode == IcsExamining) {
14162         SendToICS(ics_prefix);
14163         SendToICS("tomove black\n");
14164     }
14165 }
14166
14167 void
14168 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14169 {
14170     char buf[MSG_SIZ];
14171     ChessSquare piece = boards[0][y][x];
14172
14173     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14174
14175     switch (selection) {
14176       case ClearBoard:
14177         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14178             SendToICS(ics_prefix);
14179             SendToICS("bsetup clear\n");
14180         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14181             SendToICS(ics_prefix);
14182             SendToICS("clearboard\n");
14183         } else {
14184             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14185                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14186                 for (y = 0; y < BOARD_HEIGHT; y++) {
14187                     if (gameMode == IcsExamining) {
14188                         if (boards[currentMove][y][x] != EmptySquare) {
14189                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14190                                     AAA + x, ONE + y);
14191                             SendToICS(buf);
14192                         }
14193                     } else {
14194                         boards[0][y][x] = p;
14195                     }
14196                 }
14197             }
14198         }
14199         if (gameMode == EditPosition) {
14200             DrawPosition(FALSE, boards[0]);
14201         }
14202         break;
14203
14204       case WhitePlay:
14205         SetWhiteToPlayEvent();
14206         break;
14207
14208       case BlackPlay:
14209         SetBlackToPlayEvent();
14210         break;
14211
14212       case EmptySquare:
14213         if (gameMode == IcsExamining) {
14214             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14215             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14216             SendToICS(buf);
14217         } else {
14218             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14219                 if(x == BOARD_LEFT-2) {
14220                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14221                     boards[0][y][1] = 0;
14222                 } else
14223                 if(x == BOARD_RGHT+1) {
14224                     if(y >= gameInfo.holdingsSize) break;
14225                     boards[0][y][BOARD_WIDTH-2] = 0;
14226                 } else break;
14227             }
14228             boards[0][y][x] = EmptySquare;
14229             DrawPosition(FALSE, boards[0]);
14230         }
14231         break;
14232
14233       case PromotePiece:
14234         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14235            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14236             selection = (ChessSquare) (PROMOTED piece);
14237         } else if(piece == EmptySquare) selection = WhiteSilver;
14238         else selection = (ChessSquare)((int)piece - 1);
14239         goto defaultlabel;
14240
14241       case DemotePiece:
14242         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14243            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14244             selection = (ChessSquare) (DEMOTED piece);
14245         } else if(piece == EmptySquare) selection = BlackSilver;
14246         else selection = (ChessSquare)((int)piece + 1);
14247         goto defaultlabel;
14248
14249       case WhiteQueen:
14250       case BlackQueen:
14251         if(gameInfo.variant == VariantShatranj ||
14252            gameInfo.variant == VariantXiangqi  ||
14253            gameInfo.variant == VariantCourier  ||
14254            gameInfo.variant == VariantMakruk     )
14255             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14256         goto defaultlabel;
14257
14258       case WhiteKing:
14259       case BlackKing:
14260         if(gameInfo.variant == VariantXiangqi)
14261             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14262         if(gameInfo.variant == VariantKnightmate)
14263             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14264       default:
14265         defaultlabel:
14266         if (gameMode == IcsExamining) {
14267             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14268             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14269                      PieceToChar(selection), AAA + x, ONE + y);
14270             SendToICS(buf);
14271         } else {
14272             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14273                 int n;
14274                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14275                     n = PieceToNumber(selection - BlackPawn);
14276                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14277                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14278                     boards[0][BOARD_HEIGHT-1-n][1]++;
14279                 } else
14280                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14281                     n = PieceToNumber(selection);
14282                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14283                     boards[0][n][BOARD_WIDTH-1] = selection;
14284                     boards[0][n][BOARD_WIDTH-2]++;
14285                 }
14286             } else
14287             boards[0][y][x] = selection;
14288             DrawPosition(TRUE, boards[0]);
14289             ClearHighlights();
14290             fromX = fromY = -1;
14291         }
14292         break;
14293     }
14294 }
14295
14296
14297 void
14298 DropMenuEvent (ChessSquare selection, int x, int y)
14299 {
14300     ChessMove moveType;
14301
14302     switch (gameMode) {
14303       case IcsPlayingWhite:
14304       case MachinePlaysBlack:
14305         if (!WhiteOnMove(currentMove)) {
14306             DisplayMoveError(_("It is Black's turn"));
14307             return;
14308         }
14309         moveType = WhiteDrop;
14310         break;
14311       case IcsPlayingBlack:
14312       case MachinePlaysWhite:
14313         if (WhiteOnMove(currentMove)) {
14314             DisplayMoveError(_("It is White's turn"));
14315             return;
14316         }
14317         moveType = BlackDrop;
14318         break;
14319       case EditGame:
14320         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14321         break;
14322       default:
14323         return;
14324     }
14325
14326     if (moveType == BlackDrop && selection < BlackPawn) {
14327       selection = (ChessSquare) ((int) selection
14328                                  + (int) BlackPawn - (int) WhitePawn);
14329     }
14330     if (boards[currentMove][y][x] != EmptySquare) {
14331         DisplayMoveError(_("That square is occupied"));
14332         return;
14333     }
14334
14335     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14336 }
14337
14338 void
14339 AcceptEvent ()
14340 {
14341     /* Accept a pending offer of any kind from opponent */
14342
14343     if (appData.icsActive) {
14344         SendToICS(ics_prefix);
14345         SendToICS("accept\n");
14346     } else if (cmailMsgLoaded) {
14347         if (currentMove == cmailOldMove &&
14348             commentList[cmailOldMove] != NULL &&
14349             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14350                    "Black offers a draw" : "White offers a draw")) {
14351             TruncateGame();
14352             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14353             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14354         } else {
14355             DisplayError(_("There is no pending offer on this move"), 0);
14356             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14357         }
14358     } else {
14359         /* Not used for offers from chess program */
14360     }
14361 }
14362
14363 void
14364 DeclineEvent ()
14365 {
14366     /* Decline a pending offer of any kind from opponent */
14367
14368     if (appData.icsActive) {
14369         SendToICS(ics_prefix);
14370         SendToICS("decline\n");
14371     } else if (cmailMsgLoaded) {
14372         if (currentMove == cmailOldMove &&
14373             commentList[cmailOldMove] != NULL &&
14374             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14375                    "Black offers a draw" : "White offers a draw")) {
14376 #ifdef NOTDEF
14377             AppendComment(cmailOldMove, "Draw declined", TRUE);
14378             DisplayComment(cmailOldMove - 1, "Draw declined");
14379 #endif /*NOTDEF*/
14380         } else {
14381             DisplayError(_("There is no pending offer on this move"), 0);
14382         }
14383     } else {
14384         /* Not used for offers from chess program */
14385     }
14386 }
14387
14388 void
14389 RematchEvent ()
14390 {
14391     /* Issue ICS rematch command */
14392     if (appData.icsActive) {
14393         SendToICS(ics_prefix);
14394         SendToICS("rematch\n");
14395     }
14396 }
14397
14398 void
14399 CallFlagEvent ()
14400 {
14401     /* Call your opponent's flag (claim a win on time) */
14402     if (appData.icsActive) {
14403         SendToICS(ics_prefix);
14404         SendToICS("flag\n");
14405     } else {
14406         switch (gameMode) {
14407           default:
14408             return;
14409           case MachinePlaysWhite:
14410             if (whiteFlag) {
14411                 if (blackFlag)
14412                   GameEnds(GameIsDrawn, "Both players ran out of time",
14413                            GE_PLAYER);
14414                 else
14415                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14416             } else {
14417                 DisplayError(_("Your opponent is not out of time"), 0);
14418             }
14419             break;
14420           case MachinePlaysBlack:
14421             if (blackFlag) {
14422                 if (whiteFlag)
14423                   GameEnds(GameIsDrawn, "Both players ran out of time",
14424                            GE_PLAYER);
14425                 else
14426                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14427             } else {
14428                 DisplayError(_("Your opponent is not out of time"), 0);
14429             }
14430             break;
14431         }
14432     }
14433 }
14434
14435 void
14436 ClockClick (int which)
14437 {       // [HGM] code moved to back-end from winboard.c
14438         if(which) { // black clock
14439           if (gameMode == EditPosition || gameMode == IcsExamining) {
14440             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14441             SetBlackToPlayEvent();
14442           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14443           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14444           } else if (shiftKey) {
14445             AdjustClock(which, -1);
14446           } else if (gameMode == IcsPlayingWhite ||
14447                      gameMode == MachinePlaysBlack) {
14448             CallFlagEvent();
14449           }
14450         } else { // white clock
14451           if (gameMode == EditPosition || gameMode == IcsExamining) {
14452             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14453             SetWhiteToPlayEvent();
14454           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14455           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14456           } else if (shiftKey) {
14457             AdjustClock(which, -1);
14458           } else if (gameMode == IcsPlayingBlack ||
14459                    gameMode == MachinePlaysWhite) {
14460             CallFlagEvent();
14461           }
14462         }
14463 }
14464
14465 void
14466 DrawEvent ()
14467 {
14468     /* Offer draw or accept pending draw offer from opponent */
14469
14470     if (appData.icsActive) {
14471         /* Note: tournament rules require draw offers to be
14472            made after you make your move but before you punch
14473            your clock.  Currently ICS doesn't let you do that;
14474            instead, you immediately punch your clock after making
14475            a move, but you can offer a draw at any time. */
14476
14477         SendToICS(ics_prefix);
14478         SendToICS("draw\n");
14479         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14480     } else if (cmailMsgLoaded) {
14481         if (currentMove == cmailOldMove &&
14482             commentList[cmailOldMove] != NULL &&
14483             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14484                    "Black offers a draw" : "White offers a draw")) {
14485             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14486             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14487         } else if (currentMove == cmailOldMove + 1) {
14488             char *offer = WhiteOnMove(cmailOldMove) ?
14489               "White offers a draw" : "Black offers a draw";
14490             AppendComment(currentMove, offer, TRUE);
14491             DisplayComment(currentMove - 1, offer);
14492             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14493         } else {
14494             DisplayError(_("You must make your move before offering a draw"), 0);
14495             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14496         }
14497     } else if (first.offeredDraw) {
14498         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14499     } else {
14500         if (first.sendDrawOffers) {
14501             SendToProgram("draw\n", &first);
14502             userOfferedDraw = TRUE;
14503         }
14504     }
14505 }
14506
14507 void
14508 AdjournEvent ()
14509 {
14510     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14511
14512     if (appData.icsActive) {
14513         SendToICS(ics_prefix);
14514         SendToICS("adjourn\n");
14515     } else {
14516         /* Currently GNU Chess doesn't offer or accept Adjourns */
14517     }
14518 }
14519
14520
14521 void
14522 AbortEvent ()
14523 {
14524     /* Offer Abort or accept pending Abort offer from opponent */
14525
14526     if (appData.icsActive) {
14527         SendToICS(ics_prefix);
14528         SendToICS("abort\n");
14529     } else {
14530         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14531     }
14532 }
14533
14534 void
14535 ResignEvent ()
14536 {
14537     /* Resign.  You can do this even if it's not your turn. */
14538
14539     if (appData.icsActive) {
14540         SendToICS(ics_prefix);
14541         SendToICS("resign\n");
14542     } else {
14543         switch (gameMode) {
14544           case MachinePlaysWhite:
14545             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14546             break;
14547           case MachinePlaysBlack:
14548             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14549             break;
14550           case EditGame:
14551             if (cmailMsgLoaded) {
14552                 TruncateGame();
14553                 if (WhiteOnMove(cmailOldMove)) {
14554                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14555                 } else {
14556                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14557                 }
14558                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14559             }
14560             break;
14561           default:
14562             break;
14563         }
14564     }
14565 }
14566
14567
14568 void
14569 StopObservingEvent ()
14570 {
14571     /* Stop observing current games */
14572     SendToICS(ics_prefix);
14573     SendToICS("unobserve\n");
14574 }
14575
14576 void
14577 StopExaminingEvent ()
14578 {
14579     /* Stop observing current game */
14580     SendToICS(ics_prefix);
14581     SendToICS("unexamine\n");
14582 }
14583
14584 void
14585 ForwardInner (int target)
14586 {
14587     int limit; int oldSeekGraphUp = seekGraphUp;
14588
14589     if (appData.debugMode)
14590         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14591                 target, currentMove, forwardMostMove);
14592
14593     if (gameMode == EditPosition)
14594       return;
14595
14596     seekGraphUp = FALSE;
14597     MarkTargetSquares(1);
14598
14599     if (gameMode == PlayFromGameFile && !pausing)
14600       PauseEvent();
14601
14602     if (gameMode == IcsExamining && pausing)
14603       limit = pauseExamForwardMostMove;
14604     else
14605       limit = forwardMostMove;
14606
14607     if (target > limit) target = limit;
14608
14609     if (target > 0 && moveList[target - 1][0]) {
14610         int fromX, fromY, toX, toY;
14611         toX = moveList[target - 1][2] - AAA;
14612         toY = moveList[target - 1][3] - ONE;
14613         if (moveList[target - 1][1] == '@') {
14614             if (appData.highlightLastMove) {
14615                 SetHighlights(-1, -1, toX, toY);
14616             }
14617         } else {
14618             fromX = moveList[target - 1][0] - AAA;
14619             fromY = moveList[target - 1][1] - ONE;
14620             if (target == currentMove + 1) {
14621                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14622             }
14623             if (appData.highlightLastMove) {
14624                 SetHighlights(fromX, fromY, toX, toY);
14625             }
14626         }
14627     }
14628     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14629         gameMode == Training || gameMode == PlayFromGameFile ||
14630         gameMode == AnalyzeFile) {
14631         while (currentMove < target) {
14632             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14633             SendMoveToProgram(currentMove++, &first);
14634         }
14635     } else {
14636         currentMove = target;
14637     }
14638
14639     if (gameMode == EditGame || gameMode == EndOfGame) {
14640         whiteTimeRemaining = timeRemaining[0][currentMove];
14641         blackTimeRemaining = timeRemaining[1][currentMove];
14642     }
14643     DisplayBothClocks();
14644     DisplayMove(currentMove - 1);
14645     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14646     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14647     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14648         DisplayComment(currentMove - 1, commentList[currentMove]);
14649     }
14650     ClearMap(); // [HGM] exclude: invalidate map
14651 }
14652
14653
14654 void
14655 ForwardEvent ()
14656 {
14657     if (gameMode == IcsExamining && !pausing) {
14658         SendToICS(ics_prefix);
14659         SendToICS("forward\n");
14660     } else {
14661         ForwardInner(currentMove + 1);
14662     }
14663 }
14664
14665 void
14666 ToEndEvent ()
14667 {
14668     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14669         /* to optimze, we temporarily turn off analysis mode while we feed
14670          * the remaining moves to the engine. Otherwise we get analysis output
14671          * after each move.
14672          */
14673         if (first.analysisSupport) {
14674           SendToProgram("exit\nforce\n", &first);
14675           first.analyzing = FALSE;
14676         }
14677     }
14678
14679     if (gameMode == IcsExamining && !pausing) {
14680         SendToICS(ics_prefix);
14681         SendToICS("forward 999999\n");
14682     } else {
14683         ForwardInner(forwardMostMove);
14684     }
14685
14686     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14687         /* we have fed all the moves, so reactivate analysis mode */
14688         SendToProgram("analyze\n", &first);
14689         first.analyzing = TRUE;
14690         /*first.maybeThinking = TRUE;*/
14691         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14692     }
14693 }
14694
14695 void
14696 BackwardInner (int target)
14697 {
14698     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14699
14700     if (appData.debugMode)
14701         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14702                 target, currentMove, forwardMostMove);
14703
14704     if (gameMode == EditPosition) return;
14705     seekGraphUp = FALSE;
14706     MarkTargetSquares(1);
14707     if (currentMove <= backwardMostMove) {
14708         ClearHighlights();
14709         DrawPosition(full_redraw, boards[currentMove]);
14710         return;
14711     }
14712     if (gameMode == PlayFromGameFile && !pausing)
14713       PauseEvent();
14714
14715     if (moveList[target][0]) {
14716         int fromX, fromY, toX, toY;
14717         toX = moveList[target][2] - AAA;
14718         toY = moveList[target][3] - ONE;
14719         if (moveList[target][1] == '@') {
14720             if (appData.highlightLastMove) {
14721                 SetHighlights(-1, -1, toX, toY);
14722             }
14723         } else {
14724             fromX = moveList[target][0] - AAA;
14725             fromY = moveList[target][1] - ONE;
14726             if (target == currentMove - 1) {
14727                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14728             }
14729             if (appData.highlightLastMove) {
14730                 SetHighlights(fromX, fromY, toX, toY);
14731             }
14732         }
14733     }
14734     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14735         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14736         while (currentMove > target) {
14737             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14738                 // null move cannot be undone. Reload program with move history before it.
14739                 int i;
14740                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14741                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14742                 }
14743                 SendBoard(&first, i); 
14744               if(second.analyzing) SendBoard(&second, i);
14745                 for(currentMove=i; currentMove<target; currentMove++) {
14746                     SendMoveToProgram(currentMove, &first);
14747                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14748                 }
14749                 break;
14750             }
14751             SendToBoth("undo\n");
14752             currentMove--;
14753         }
14754     } else {
14755         currentMove = target;
14756     }
14757
14758     if (gameMode == EditGame || gameMode == EndOfGame) {
14759         whiteTimeRemaining = timeRemaining[0][currentMove];
14760         blackTimeRemaining = timeRemaining[1][currentMove];
14761     }
14762     DisplayBothClocks();
14763     DisplayMove(currentMove - 1);
14764     DrawPosition(full_redraw, boards[currentMove]);
14765     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14766     // [HGM] PV info: routine tests if comment empty
14767     DisplayComment(currentMove - 1, commentList[currentMove]);
14768     ClearMap(); // [HGM] exclude: invalidate map
14769 }
14770
14771 void
14772 BackwardEvent ()
14773 {
14774     if (gameMode == IcsExamining && !pausing) {
14775         SendToICS(ics_prefix);
14776         SendToICS("backward\n");
14777     } else {
14778         BackwardInner(currentMove - 1);
14779     }
14780 }
14781
14782 void
14783 ToStartEvent ()
14784 {
14785     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14786         /* to optimize, we temporarily turn off analysis mode while we undo
14787          * all the moves. Otherwise we get analysis output after each undo.
14788          */
14789         if (first.analysisSupport) {
14790           SendToProgram("exit\nforce\n", &first);
14791           first.analyzing = FALSE;
14792         }
14793     }
14794
14795     if (gameMode == IcsExamining && !pausing) {
14796         SendToICS(ics_prefix);
14797         SendToICS("backward 999999\n");
14798     } else {
14799         BackwardInner(backwardMostMove);
14800     }
14801
14802     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14803         /* we have fed all the moves, so reactivate analysis mode */
14804         SendToProgram("analyze\n", &first);
14805         first.analyzing = TRUE;
14806         /*first.maybeThinking = TRUE;*/
14807         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14808     }
14809 }
14810
14811 void
14812 ToNrEvent (int to)
14813 {
14814   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14815   if (to >= forwardMostMove) to = forwardMostMove;
14816   if (to <= backwardMostMove) to = backwardMostMove;
14817   if (to < currentMove) {
14818     BackwardInner(to);
14819   } else {
14820     ForwardInner(to);
14821   }
14822 }
14823
14824 void
14825 RevertEvent (Boolean annotate)
14826 {
14827     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14828         return;
14829     }
14830     if (gameMode != IcsExamining) {
14831         DisplayError(_("You are not examining a game"), 0);
14832         return;
14833     }
14834     if (pausing) {
14835         DisplayError(_("You can't revert while pausing"), 0);
14836         return;
14837     }
14838     SendToICS(ics_prefix);
14839     SendToICS("revert\n");
14840 }
14841
14842 void
14843 RetractMoveEvent ()
14844 {
14845     switch (gameMode) {
14846       case MachinePlaysWhite:
14847       case MachinePlaysBlack:
14848         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14849             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14850             return;
14851         }
14852         if (forwardMostMove < 2) return;
14853         currentMove = forwardMostMove = forwardMostMove - 2;
14854         whiteTimeRemaining = timeRemaining[0][currentMove];
14855         blackTimeRemaining = timeRemaining[1][currentMove];
14856         DisplayBothClocks();
14857         DisplayMove(currentMove - 1);
14858         ClearHighlights();/*!! could figure this out*/
14859         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14860         SendToProgram("remove\n", &first);
14861         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14862         break;
14863
14864       case BeginningOfGame:
14865       default:
14866         break;
14867
14868       case IcsPlayingWhite:
14869       case IcsPlayingBlack:
14870         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14871             SendToICS(ics_prefix);
14872             SendToICS("takeback 2\n");
14873         } else {
14874             SendToICS(ics_prefix);
14875             SendToICS("takeback 1\n");
14876         }
14877         break;
14878     }
14879 }
14880
14881 void
14882 MoveNowEvent ()
14883 {
14884     ChessProgramState *cps;
14885
14886     switch (gameMode) {
14887       case MachinePlaysWhite:
14888         if (!WhiteOnMove(forwardMostMove)) {
14889             DisplayError(_("It is your turn"), 0);
14890             return;
14891         }
14892         cps = &first;
14893         break;
14894       case MachinePlaysBlack:
14895         if (WhiteOnMove(forwardMostMove)) {
14896             DisplayError(_("It is your turn"), 0);
14897             return;
14898         }
14899         cps = &first;
14900         break;
14901       case TwoMachinesPlay:
14902         if (WhiteOnMove(forwardMostMove) ==
14903             (first.twoMachinesColor[0] == 'w')) {
14904             cps = &first;
14905         } else {
14906             cps = &second;
14907         }
14908         break;
14909       case BeginningOfGame:
14910       default:
14911         return;
14912     }
14913     SendToProgram("?\n", cps);
14914 }
14915
14916 void
14917 TruncateGameEvent ()
14918 {
14919     EditGameEvent();
14920     if (gameMode != EditGame) return;
14921     TruncateGame();
14922 }
14923
14924 void
14925 TruncateGame ()
14926 {
14927     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14928     if (forwardMostMove > currentMove) {
14929         if (gameInfo.resultDetails != NULL) {
14930             free(gameInfo.resultDetails);
14931             gameInfo.resultDetails = NULL;
14932             gameInfo.result = GameUnfinished;
14933         }
14934         forwardMostMove = currentMove;
14935         HistorySet(parseList, backwardMostMove, forwardMostMove,
14936                    currentMove-1);
14937     }
14938 }
14939
14940 void
14941 HintEvent ()
14942 {
14943     if (appData.noChessProgram) return;
14944     switch (gameMode) {
14945       case MachinePlaysWhite:
14946         if (WhiteOnMove(forwardMostMove)) {
14947             DisplayError(_("Wait until your turn"), 0);
14948             return;
14949         }
14950         break;
14951       case BeginningOfGame:
14952       case MachinePlaysBlack:
14953         if (!WhiteOnMove(forwardMostMove)) {
14954             DisplayError(_("Wait until your turn"), 0);
14955             return;
14956         }
14957         break;
14958       default:
14959         DisplayError(_("No hint available"), 0);
14960         return;
14961     }
14962     SendToProgram("hint\n", &first);
14963     hintRequested = TRUE;
14964 }
14965
14966 void
14967 BookEvent ()
14968 {
14969     if (appData.noChessProgram) return;
14970     switch (gameMode) {
14971       case MachinePlaysWhite:
14972         if (WhiteOnMove(forwardMostMove)) {
14973             DisplayError(_("Wait until your turn"), 0);
14974             return;
14975         }
14976         break;
14977       case BeginningOfGame:
14978       case MachinePlaysBlack:
14979         if (!WhiteOnMove(forwardMostMove)) {
14980             DisplayError(_("Wait until your turn"), 0);
14981             return;
14982         }
14983         break;
14984       case EditPosition:
14985         EditPositionDone(TRUE);
14986         break;
14987       case TwoMachinesPlay:
14988         return;
14989       default:
14990         break;
14991     }
14992     SendToProgram("bk\n", &first);
14993     bookOutput[0] = NULLCHAR;
14994     bookRequested = TRUE;
14995 }
14996
14997 void
14998 AboutGameEvent ()
14999 {
15000     char *tags = PGNTags(&gameInfo);
15001     TagsPopUp(tags, CmailMsg());
15002     free(tags);
15003 }
15004
15005 /* end button procedures */
15006
15007 void
15008 PrintPosition (FILE *fp, int move)
15009 {
15010     int i, j;
15011
15012     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15013         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15014             char c = PieceToChar(boards[move][i][j]);
15015             fputc(c == 'x' ? '.' : c, fp);
15016             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15017         }
15018     }
15019     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15020       fprintf(fp, "white to play\n");
15021     else
15022       fprintf(fp, "black to play\n");
15023 }
15024
15025 void
15026 PrintOpponents (FILE *fp)
15027 {
15028     if (gameInfo.white != NULL) {
15029         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15030     } else {
15031         fprintf(fp, "\n");
15032     }
15033 }
15034
15035 /* Find last component of program's own name, using some heuristics */
15036 void
15037 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15038 {
15039     char *p, *q, c;
15040     int local = (strcmp(host, "localhost") == 0);
15041     while (!local && (p = strchr(prog, ';')) != NULL) {
15042         p++;
15043         while (*p == ' ') p++;
15044         prog = p;
15045     }
15046     if (*prog == '"' || *prog == '\'') {
15047         q = strchr(prog + 1, *prog);
15048     } else {
15049         q = strchr(prog, ' ');
15050     }
15051     if (q == NULL) q = prog + strlen(prog);
15052     p = q;
15053     while (p >= prog && *p != '/' && *p != '\\') p--;
15054     p++;
15055     if(p == prog && *p == '"') p++;
15056     c = *q; *q = 0;
15057     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15058     memcpy(buf, p, q - p);
15059     buf[q - p] = NULLCHAR;
15060     if (!local) {
15061         strcat(buf, "@");
15062         strcat(buf, host);
15063     }
15064 }
15065
15066 char *
15067 TimeControlTagValue ()
15068 {
15069     char buf[MSG_SIZ];
15070     if (!appData.clockMode) {
15071       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15072     } else if (movesPerSession > 0) {
15073       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15074     } else if (timeIncrement == 0) {
15075       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15076     } else {
15077       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15078     }
15079     return StrSave(buf);
15080 }
15081
15082 void
15083 SetGameInfo ()
15084 {
15085     /* This routine is used only for certain modes */
15086     VariantClass v = gameInfo.variant;
15087     ChessMove r = GameUnfinished;
15088     char *p = NULL;
15089
15090     if(keepInfo) return;
15091
15092     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15093         r = gameInfo.result;
15094         p = gameInfo.resultDetails;
15095         gameInfo.resultDetails = NULL;
15096     }
15097     ClearGameInfo(&gameInfo);
15098     gameInfo.variant = v;
15099
15100     switch (gameMode) {
15101       case MachinePlaysWhite:
15102         gameInfo.event = StrSave( appData.pgnEventHeader );
15103         gameInfo.site = StrSave(HostName());
15104         gameInfo.date = PGNDate();
15105         gameInfo.round = StrSave("-");
15106         gameInfo.white = StrSave(first.tidy);
15107         gameInfo.black = StrSave(UserName());
15108         gameInfo.timeControl = TimeControlTagValue();
15109         break;
15110
15111       case MachinePlaysBlack:
15112         gameInfo.event = StrSave( appData.pgnEventHeader );
15113         gameInfo.site = StrSave(HostName());
15114         gameInfo.date = PGNDate();
15115         gameInfo.round = StrSave("-");
15116         gameInfo.white = StrSave(UserName());
15117         gameInfo.black = StrSave(first.tidy);
15118         gameInfo.timeControl = TimeControlTagValue();
15119         break;
15120
15121       case TwoMachinesPlay:
15122         gameInfo.event = StrSave( appData.pgnEventHeader );
15123         gameInfo.site = StrSave(HostName());
15124         gameInfo.date = PGNDate();
15125         if (roundNr > 0) {
15126             char buf[MSG_SIZ];
15127             snprintf(buf, MSG_SIZ, "%d", roundNr);
15128             gameInfo.round = StrSave(buf);
15129         } else {
15130             gameInfo.round = StrSave("-");
15131         }
15132         if (first.twoMachinesColor[0] == 'w') {
15133             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15134             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15135         } else {
15136             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15137             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15138         }
15139         gameInfo.timeControl = TimeControlTagValue();
15140         break;
15141
15142       case EditGame:
15143         gameInfo.event = StrSave("Edited game");
15144         gameInfo.site = StrSave(HostName());
15145         gameInfo.date = PGNDate();
15146         gameInfo.round = StrSave("-");
15147         gameInfo.white = StrSave("-");
15148         gameInfo.black = StrSave("-");
15149         gameInfo.result = r;
15150         gameInfo.resultDetails = p;
15151         break;
15152
15153       case EditPosition:
15154         gameInfo.event = StrSave("Edited position");
15155         gameInfo.site = StrSave(HostName());
15156         gameInfo.date = PGNDate();
15157         gameInfo.round = StrSave("-");
15158         gameInfo.white = StrSave("-");
15159         gameInfo.black = StrSave("-");
15160         break;
15161
15162       case IcsPlayingWhite:
15163       case IcsPlayingBlack:
15164       case IcsObserving:
15165       case IcsExamining:
15166         break;
15167
15168       case PlayFromGameFile:
15169         gameInfo.event = StrSave("Game from non-PGN file");
15170         gameInfo.site = StrSave(HostName());
15171         gameInfo.date = PGNDate();
15172         gameInfo.round = StrSave("-");
15173         gameInfo.white = StrSave("?");
15174         gameInfo.black = StrSave("?");
15175         break;
15176
15177       default:
15178         break;
15179     }
15180 }
15181
15182 void
15183 ReplaceComment (int index, char *text)
15184 {
15185     int len;
15186     char *p;
15187     float score;
15188
15189     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15190        pvInfoList[index-1].depth == len &&
15191        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15192        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15193     while (*text == '\n') text++;
15194     len = strlen(text);
15195     while (len > 0 && text[len - 1] == '\n') len--;
15196
15197     if (commentList[index] != NULL)
15198       free(commentList[index]);
15199
15200     if (len == 0) {
15201         commentList[index] = NULL;
15202         return;
15203     }
15204   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15205       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15206       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15207     commentList[index] = (char *) malloc(len + 2);
15208     strncpy(commentList[index], text, len);
15209     commentList[index][len] = '\n';
15210     commentList[index][len + 1] = NULLCHAR;
15211   } else {
15212     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15213     char *p;
15214     commentList[index] = (char *) malloc(len + 7);
15215     safeStrCpy(commentList[index], "{\n", 3);
15216     safeStrCpy(commentList[index]+2, text, len+1);
15217     commentList[index][len+2] = NULLCHAR;
15218     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15219     strcat(commentList[index], "\n}\n");
15220   }
15221 }
15222
15223 void
15224 CrushCRs (char *text)
15225 {
15226   char *p = text;
15227   char *q = text;
15228   char ch;
15229
15230   do {
15231     ch = *p++;
15232     if (ch == '\r') continue;
15233     *q++ = ch;
15234   } while (ch != '\0');
15235 }
15236
15237 void
15238 AppendComment (int index, char *text, Boolean addBraces)
15239 /* addBraces  tells if we should add {} */
15240 {
15241     int oldlen, len;
15242     char *old;
15243
15244 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15245     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15246
15247     CrushCRs(text);
15248     while (*text == '\n') text++;
15249     len = strlen(text);
15250     while (len > 0 && text[len - 1] == '\n') len--;
15251     text[len] = NULLCHAR;
15252
15253     if (len == 0) return;
15254
15255     if (commentList[index] != NULL) {
15256       Boolean addClosingBrace = addBraces;
15257         old = commentList[index];
15258         oldlen = strlen(old);
15259         while(commentList[index][oldlen-1] ==  '\n')
15260           commentList[index][--oldlen] = NULLCHAR;
15261         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15262         safeStrCpy(commentList[index], old, oldlen + len + 6);
15263         free(old);
15264         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15265         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15266           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15267           while (*text == '\n') { text++; len--; }
15268           commentList[index][--oldlen] = NULLCHAR;
15269       }
15270         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15271         else          strcat(commentList[index], "\n");
15272         strcat(commentList[index], text);
15273         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15274         else          strcat(commentList[index], "\n");
15275     } else {
15276         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15277         if(addBraces)
15278           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15279         else commentList[index][0] = NULLCHAR;
15280         strcat(commentList[index], text);
15281         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15282         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15283     }
15284 }
15285
15286 static char *
15287 FindStr (char * text, char * sub_text)
15288 {
15289     char * result = strstr( text, sub_text );
15290
15291     if( result != NULL ) {
15292         result += strlen( sub_text );
15293     }
15294
15295     return result;
15296 }
15297
15298 /* [AS] Try to extract PV info from PGN comment */
15299 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15300 char *
15301 GetInfoFromComment (int index, char * text)
15302 {
15303     char * sep = text, *p;
15304
15305     if( text != NULL && index > 0 ) {
15306         int score = 0;
15307         int depth = 0;
15308         int time = -1, sec = 0, deci;
15309         char * s_eval = FindStr( text, "[%eval " );
15310         char * s_emt = FindStr( text, "[%emt " );
15311
15312         if( s_eval != NULL || s_emt != NULL ) {
15313             /* New style */
15314             char delim;
15315
15316             if( s_eval != NULL ) {
15317                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15318                     return text;
15319                 }
15320
15321                 if( delim != ']' ) {
15322                     return text;
15323                 }
15324             }
15325
15326             if( s_emt != NULL ) {
15327             }
15328                 return text;
15329         }
15330         else {
15331             /* We expect something like: [+|-]nnn.nn/dd */
15332             int score_lo = 0;
15333
15334             if(*text != '{') return text; // [HGM] braces: must be normal comment
15335
15336             sep = strchr( text, '/' );
15337             if( sep == NULL || sep < (text+4) ) {
15338                 return text;
15339             }
15340
15341             p = text;
15342             if(p[1] == '(') { // comment starts with PV
15343                p = strchr(p, ')'); // locate end of PV
15344                if(p == NULL || sep < p+5) return text;
15345                // at this point we have something like "{(.*) +0.23/6 ..."
15346                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15347                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15348                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15349             }
15350             time = -1; sec = -1; deci = -1;
15351             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15352                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15353                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15354                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15355                 return text;
15356             }
15357
15358             if( score_lo < 0 || score_lo >= 100 ) {
15359                 return text;
15360             }
15361
15362             if(sec >= 0) time = 600*time + 10*sec; else
15363             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15364
15365             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15366
15367             /* [HGM] PV time: now locate end of PV info */
15368             while( *++sep >= '0' && *sep <= '9'); // strip depth
15369             if(time >= 0)
15370             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15371             if(sec >= 0)
15372             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15373             if(deci >= 0)
15374             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15375             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15376         }
15377
15378         if( depth <= 0 ) {
15379             return text;
15380         }
15381
15382         if( time < 0 ) {
15383             time = -1;
15384         }
15385
15386         pvInfoList[index-1].depth = depth;
15387         pvInfoList[index-1].score = score;
15388         pvInfoList[index-1].time  = 10*time; // centi-sec
15389         if(*sep == '}') *sep = 0; else *--sep = '{';
15390         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15391     }
15392     return sep;
15393 }
15394
15395 void
15396 SendToProgram (char *message, ChessProgramState *cps)
15397 {
15398     int count, outCount, error;
15399     char buf[MSG_SIZ];
15400
15401     if (cps->pr == NoProc) return;
15402     Attention(cps);
15403
15404     if (appData.debugMode) {
15405         TimeMark now;
15406         GetTimeMark(&now);
15407         fprintf(debugFP, "%ld >%-6s: %s",
15408                 SubtractTimeMarks(&now, &programStartTime),
15409                 cps->which, message);
15410         if(serverFP)
15411             fprintf(serverFP, "%ld >%-6s: %s",
15412                 SubtractTimeMarks(&now, &programStartTime),
15413                 cps->which, message), fflush(serverFP);
15414     }
15415
15416     count = strlen(message);
15417     outCount = OutputToProcess(cps->pr, message, count, &error);
15418     if (outCount < count && !exiting
15419                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15420       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15421       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15422         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15423             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15424                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15425                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15426                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15427             } else {
15428                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15429                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15430                 gameInfo.result = res;
15431             }
15432             gameInfo.resultDetails = StrSave(buf);
15433         }
15434         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15435         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15436     }
15437 }
15438
15439 void
15440 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15441 {
15442     char *end_str;
15443     char buf[MSG_SIZ];
15444     ChessProgramState *cps = (ChessProgramState *)closure;
15445
15446     if (isr != cps->isr) return; /* Killed intentionally */
15447     if (count <= 0) {
15448         if (count == 0) {
15449             RemoveInputSource(cps->isr);
15450             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15451                     _(cps->which), cps->program);
15452             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15453             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15454                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15455                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15456                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15457                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15458                 } else {
15459                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15460                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15461                     gameInfo.result = res;
15462                 }
15463                 gameInfo.resultDetails = StrSave(buf);
15464             }
15465             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15466             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15467         } else {
15468             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15469                     _(cps->which), cps->program);
15470             RemoveInputSource(cps->isr);
15471
15472             /* [AS] Program is misbehaving badly... kill it */
15473             if( count == -2 ) {
15474                 DestroyChildProcess( cps->pr, 9 );
15475                 cps->pr = NoProc;
15476             }
15477
15478             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15479         }
15480         return;
15481     }
15482
15483     if ((end_str = strchr(message, '\r')) != NULL)
15484       *end_str = NULLCHAR;
15485     if ((end_str = strchr(message, '\n')) != NULL)
15486       *end_str = NULLCHAR;
15487
15488     if (appData.debugMode) {
15489         TimeMark now; int print = 1;
15490         char *quote = ""; char c; int i;
15491
15492         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15493                 char start = message[0];
15494                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15495                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15496                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15497                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15498                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15499                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15500                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15501                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15502                    sscanf(message, "hint: %c", &c)!=1 && 
15503                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15504                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15505                     print = (appData.engineComments >= 2);
15506                 }
15507                 message[0] = start; // restore original message
15508         }
15509         if(print) {
15510                 GetTimeMark(&now);
15511                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15512                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15513                         quote,
15514                         message);
15515                 if(serverFP)
15516                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15517                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15518                         quote,
15519                         message), fflush(serverFP);
15520         }
15521     }
15522
15523     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15524     if (appData.icsEngineAnalyze) {
15525         if (strstr(message, "whisper") != NULL ||
15526              strstr(message, "kibitz") != NULL ||
15527             strstr(message, "tellics") != NULL) return;
15528     }
15529
15530     HandleMachineMove(message, cps);
15531 }
15532
15533
15534 void
15535 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15536 {
15537     char buf[MSG_SIZ];
15538     int seconds;
15539
15540     if( timeControl_2 > 0 ) {
15541         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15542             tc = timeControl_2;
15543         }
15544     }
15545     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15546     inc /= cps->timeOdds;
15547     st  /= cps->timeOdds;
15548
15549     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15550
15551     if (st > 0) {
15552       /* Set exact time per move, normally using st command */
15553       if (cps->stKludge) {
15554         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15555         seconds = st % 60;
15556         if (seconds == 0) {
15557           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15558         } else {
15559           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15560         }
15561       } else {
15562         snprintf(buf, MSG_SIZ, "st %d\n", st);
15563       }
15564     } else {
15565       /* Set conventional or incremental time control, using level command */
15566       if (seconds == 0) {
15567         /* Note old gnuchess bug -- minutes:seconds used to not work.
15568            Fixed in later versions, but still avoid :seconds
15569            when seconds is 0. */
15570         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15571       } else {
15572         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15573                  seconds, inc/1000.);
15574       }
15575     }
15576     SendToProgram(buf, cps);
15577
15578     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15579     /* Orthogonally, limit search to given depth */
15580     if (sd > 0) {
15581       if (cps->sdKludge) {
15582         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15583       } else {
15584         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15585       }
15586       SendToProgram(buf, cps);
15587     }
15588
15589     if(cps->nps >= 0) { /* [HGM] nps */
15590         if(cps->supportsNPS == FALSE)
15591           cps->nps = -1; // don't use if engine explicitly says not supported!
15592         else {
15593           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15594           SendToProgram(buf, cps);
15595         }
15596     }
15597 }
15598
15599 ChessProgramState *
15600 WhitePlayer ()
15601 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15602 {
15603     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15604        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15605         return &second;
15606     return &first;
15607 }
15608
15609 void
15610 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15611 {
15612     char message[MSG_SIZ];
15613     long time, otime;
15614
15615     /* Note: this routine must be called when the clocks are stopped
15616        or when they have *just* been set or switched; otherwise
15617        it will be off by the time since the current tick started.
15618     */
15619     if (machineWhite) {
15620         time = whiteTimeRemaining / 10;
15621         otime = blackTimeRemaining / 10;
15622     } else {
15623         time = blackTimeRemaining / 10;
15624         otime = whiteTimeRemaining / 10;
15625     }
15626     /* [HGM] translate opponent's time by time-odds factor */
15627     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15628
15629     if (time <= 0) time = 1;
15630     if (otime <= 0) otime = 1;
15631
15632     snprintf(message, MSG_SIZ, "time %ld\n", time);
15633     SendToProgram(message, cps);
15634
15635     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15636     SendToProgram(message, cps);
15637 }
15638
15639 int
15640 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15641 {
15642   char buf[MSG_SIZ];
15643   int len = strlen(name);
15644   int val;
15645
15646   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15647     (*p) += len + 1;
15648     sscanf(*p, "%d", &val);
15649     *loc = (val != 0);
15650     while (**p && **p != ' ')
15651       (*p)++;
15652     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15653     SendToProgram(buf, cps);
15654     return TRUE;
15655   }
15656   return FALSE;
15657 }
15658
15659 int
15660 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15661 {
15662   char buf[MSG_SIZ];
15663   int len = strlen(name);
15664   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15665     (*p) += len + 1;
15666     sscanf(*p, "%d", loc);
15667     while (**p && **p != ' ') (*p)++;
15668     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15669     SendToProgram(buf, cps);
15670     return TRUE;
15671   }
15672   return FALSE;
15673 }
15674
15675 int
15676 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15677 {
15678   char buf[MSG_SIZ];
15679   int len = strlen(name);
15680   if (strncmp((*p), name, len) == 0
15681       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15682     (*p) += len + 2;
15683     sscanf(*p, "%[^\"]", loc);
15684     while (**p && **p != '\"') (*p)++;
15685     if (**p == '\"') (*p)++;
15686     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15687     SendToProgram(buf, cps);
15688     return TRUE;
15689   }
15690   return FALSE;
15691 }
15692
15693 int
15694 ParseOption (Option *opt, ChessProgramState *cps)
15695 // [HGM] options: process the string that defines an engine option, and determine
15696 // name, type, default value, and allowed value range
15697 {
15698         char *p, *q, buf[MSG_SIZ];
15699         int n, min = (-1)<<31, max = 1<<31, def;
15700
15701         if(p = strstr(opt->name, " -spin ")) {
15702             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15703             if(max < min) max = min; // enforce consistency
15704             if(def < min) def = min;
15705             if(def > max) def = max;
15706             opt->value = def;
15707             opt->min = min;
15708             opt->max = max;
15709             opt->type = Spin;
15710         } else if((p = strstr(opt->name, " -slider "))) {
15711             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15712             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15713             if(max < min) max = min; // enforce consistency
15714             if(def < min) def = min;
15715             if(def > max) def = max;
15716             opt->value = def;
15717             opt->min = min;
15718             opt->max = max;
15719             opt->type = Spin; // Slider;
15720         } else if((p = strstr(opt->name, " -string "))) {
15721             opt->textValue = p+9;
15722             opt->type = TextBox;
15723         } else if((p = strstr(opt->name, " -file "))) {
15724             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15725             opt->textValue = p+7;
15726             opt->type = FileName; // FileName;
15727         } else if((p = strstr(opt->name, " -path "))) {
15728             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15729             opt->textValue = p+7;
15730             opt->type = PathName; // PathName;
15731         } else if(p = strstr(opt->name, " -check ")) {
15732             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15733             opt->value = (def != 0);
15734             opt->type = CheckBox;
15735         } else if(p = strstr(opt->name, " -combo ")) {
15736             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15737             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15738             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15739             opt->value = n = 0;
15740             while(q = StrStr(q, " /// ")) {
15741                 n++; *q = 0;    // count choices, and null-terminate each of them
15742                 q += 5;
15743                 if(*q == '*') { // remember default, which is marked with * prefix
15744                     q++;
15745                     opt->value = n;
15746                 }
15747                 cps->comboList[cps->comboCnt++] = q;
15748             }
15749             cps->comboList[cps->comboCnt++] = NULL;
15750             opt->max = n + 1;
15751             opt->type = ComboBox;
15752         } else if(p = strstr(opt->name, " -button")) {
15753             opt->type = Button;
15754         } else if(p = strstr(opt->name, " -save")) {
15755             opt->type = SaveButton;
15756         } else return FALSE;
15757         *p = 0; // terminate option name
15758         // now look if the command-line options define a setting for this engine option.
15759         if(cps->optionSettings && cps->optionSettings[0])
15760             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15761         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15762           snprintf(buf, MSG_SIZ, "option %s", p);
15763                 if(p = strstr(buf, ",")) *p = 0;
15764                 if(q = strchr(buf, '=')) switch(opt->type) {
15765                     case ComboBox:
15766                         for(n=0; n<opt->max; n++)
15767                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15768                         break;
15769                     case TextBox:
15770                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15771                         break;
15772                     case Spin:
15773                     case CheckBox:
15774                         opt->value = atoi(q+1);
15775                     default:
15776                         break;
15777                 }
15778                 strcat(buf, "\n");
15779                 SendToProgram(buf, cps);
15780         }
15781         return TRUE;
15782 }
15783
15784 void
15785 FeatureDone (ChessProgramState *cps, int val)
15786 {
15787   DelayedEventCallback cb = GetDelayedEvent();
15788   if ((cb == InitBackEnd3 && cps == &first) ||
15789       (cb == SettingsMenuIfReady && cps == &second) ||
15790       (cb == LoadEngine) ||
15791       (cb == TwoMachinesEventIfReady)) {
15792     CancelDelayedEvent();
15793     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15794   }
15795   cps->initDone = val;
15796 }
15797
15798 /* Parse feature command from engine */
15799 void
15800 ParseFeatures (char *args, ChessProgramState *cps)
15801 {
15802   char *p = args;
15803   char *q;
15804   int val;
15805   char buf[MSG_SIZ];
15806
15807   for (;;) {
15808     while (*p == ' ') p++;
15809     if (*p == NULLCHAR) return;
15810
15811     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15812     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15813     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15814     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15815     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15816     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15817     if (BoolFeature(&p, "reuse", &val, cps)) {
15818       /* Engine can disable reuse, but can't enable it if user said no */
15819       if (!val) cps->reuse = FALSE;
15820       continue;
15821     }
15822     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15823     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15824       if (gameMode == TwoMachinesPlay) {
15825         DisplayTwoMachinesTitle();
15826       } else {
15827         DisplayTitle("");
15828       }
15829       continue;
15830     }
15831     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15832     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15833     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15834     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15835     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15836     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15837     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15838     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15839     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15840     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15841     if (IntFeature(&p, "done", &val, cps)) {
15842       FeatureDone(cps, val);
15843       continue;
15844     }
15845     /* Added by Tord: */
15846     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15847     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15848     /* End of additions by Tord */
15849
15850     /* [HGM] added features: */
15851     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15852     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15853     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15854     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15855     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15856     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15857     if (StringFeature(&p, "option", buf, cps)) {
15858         FREE(cps->option[cps->nrOptions].name);
15859         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15860         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15861         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15862           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15863             SendToProgram(buf, cps);
15864             continue;
15865         }
15866         if(cps->nrOptions >= MAX_OPTIONS) {
15867             cps->nrOptions--;
15868             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15869             DisplayError(buf, 0);
15870         }
15871         continue;
15872     }
15873     /* End of additions by HGM */
15874
15875     /* unknown feature: complain and skip */
15876     q = p;
15877     while (*q && *q != '=') q++;
15878     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15879     SendToProgram(buf, cps);
15880     p = q;
15881     if (*p == '=') {
15882       p++;
15883       if (*p == '\"') {
15884         p++;
15885         while (*p && *p != '\"') p++;
15886         if (*p == '\"') p++;
15887       } else {
15888         while (*p && *p != ' ') p++;
15889       }
15890     }
15891   }
15892
15893 }
15894
15895 void
15896 PeriodicUpdatesEvent (int newState)
15897 {
15898     if (newState == appData.periodicUpdates)
15899       return;
15900
15901     appData.periodicUpdates=newState;
15902
15903     /* Display type changes, so update it now */
15904 //    DisplayAnalysis();
15905
15906     /* Get the ball rolling again... */
15907     if (newState) {
15908         AnalysisPeriodicEvent(1);
15909         StartAnalysisClock();
15910     }
15911 }
15912
15913 void
15914 PonderNextMoveEvent (int newState)
15915 {
15916     if (newState == appData.ponderNextMove) return;
15917     if (gameMode == EditPosition) EditPositionDone(TRUE);
15918     if (newState) {
15919         SendToProgram("hard\n", &first);
15920         if (gameMode == TwoMachinesPlay) {
15921             SendToProgram("hard\n", &second);
15922         }
15923     } else {
15924         SendToProgram("easy\n", &first);
15925         thinkOutput[0] = NULLCHAR;
15926         if (gameMode == TwoMachinesPlay) {
15927             SendToProgram("easy\n", &second);
15928         }
15929     }
15930     appData.ponderNextMove = newState;
15931 }
15932
15933 void
15934 NewSettingEvent (int option, int *feature, char *command, int value)
15935 {
15936     char buf[MSG_SIZ];
15937
15938     if (gameMode == EditPosition) EditPositionDone(TRUE);
15939     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15940     if(feature == NULL || *feature) SendToProgram(buf, &first);
15941     if (gameMode == TwoMachinesPlay) {
15942         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15943     }
15944 }
15945
15946 void
15947 ShowThinkingEvent ()
15948 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15949 {
15950     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15951     int newState = appData.showThinking
15952         // [HGM] thinking: other features now need thinking output as well
15953         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15954
15955     if (oldState == newState) return;
15956     oldState = newState;
15957     if (gameMode == EditPosition) EditPositionDone(TRUE);
15958     if (oldState) {
15959         SendToProgram("post\n", &first);
15960         if (gameMode == TwoMachinesPlay) {
15961             SendToProgram("post\n", &second);
15962         }
15963     } else {
15964         SendToProgram("nopost\n", &first);
15965         thinkOutput[0] = NULLCHAR;
15966         if (gameMode == TwoMachinesPlay) {
15967             SendToProgram("nopost\n", &second);
15968         }
15969     }
15970 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15971 }
15972
15973 void
15974 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15975 {
15976   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15977   if (pr == NoProc) return;
15978   AskQuestion(title, question, replyPrefix, pr);
15979 }
15980
15981 void
15982 TypeInEvent (char firstChar)
15983 {
15984     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15985         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15986         gameMode == AnalyzeMode || gameMode == EditGame || 
15987         gameMode == EditPosition || gameMode == IcsExamining ||
15988         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15989         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15990                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15991                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15992         gameMode == Training) PopUpMoveDialog(firstChar);
15993 }
15994
15995 void
15996 TypeInDoneEvent (char *move)
15997 {
15998         Board board;
15999         int n, fromX, fromY, toX, toY;
16000         char promoChar;
16001         ChessMove moveType;
16002
16003         // [HGM] FENedit
16004         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
16005                 EditPositionPasteFEN(move);
16006                 return;
16007         }
16008         // [HGM] movenum: allow move number to be typed in any mode
16009         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16010           ToNrEvent(2*n-1);
16011           return;
16012         }
16013         // undocumented kludge: allow command-line option to be typed in!
16014         // (potentially fatal, and does not implement the effect of the option.)
16015         // should only be used for options that are values on which future decisions will be made,
16016         // and definitely not on options that would be used during initialization.
16017         if(strstr(move, "!!! -") == move) {
16018             ParseArgsFromString(move+4);
16019             return;
16020         }
16021
16022       if (gameMode != EditGame && currentMove != forwardMostMove && 
16023         gameMode != Training) {
16024         DisplayMoveError(_("Displayed move is not current"));
16025       } else {
16026         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16027           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16028         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16029         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16030           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16031           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16032         } else {
16033           DisplayMoveError(_("Could not parse move"));
16034         }
16035       }
16036 }
16037
16038 void
16039 DisplayMove (int moveNumber)
16040 {
16041     char message[MSG_SIZ];
16042     char res[MSG_SIZ];
16043     char cpThinkOutput[MSG_SIZ];
16044
16045     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16046
16047     if (moveNumber == forwardMostMove - 1 ||
16048         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16049
16050         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16051
16052         if (strchr(cpThinkOutput, '\n')) {
16053             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16054         }
16055     } else {
16056         *cpThinkOutput = NULLCHAR;
16057     }
16058
16059     /* [AS] Hide thinking from human user */
16060     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16061         *cpThinkOutput = NULLCHAR;
16062         if( thinkOutput[0] != NULLCHAR ) {
16063             int i;
16064
16065             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16066                 cpThinkOutput[i] = '.';
16067             }
16068             cpThinkOutput[i] = NULLCHAR;
16069             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16070         }
16071     }
16072
16073     if (moveNumber == forwardMostMove - 1 &&
16074         gameInfo.resultDetails != NULL) {
16075         if (gameInfo.resultDetails[0] == NULLCHAR) {
16076           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16077         } else {
16078           snprintf(res, MSG_SIZ, " {%s} %s",
16079                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16080         }
16081     } else {
16082         res[0] = NULLCHAR;
16083     }
16084
16085     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16086         DisplayMessage(res, cpThinkOutput);
16087     } else {
16088       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16089                 WhiteOnMove(moveNumber) ? " " : ".. ",
16090                 parseList[moveNumber], res);
16091         DisplayMessage(message, cpThinkOutput);
16092     }
16093 }
16094
16095 void
16096 DisplayComment (int moveNumber, char *text)
16097 {
16098     char title[MSG_SIZ];
16099
16100     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16101       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16102     } else {
16103       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16104               WhiteOnMove(moveNumber) ? " " : ".. ",
16105               parseList[moveNumber]);
16106     }
16107     if (text != NULL && (appData.autoDisplayComment || commentUp))
16108         CommentPopUp(title, text);
16109 }
16110
16111 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16112  * might be busy thinking or pondering.  It can be omitted if your
16113  * gnuchess is configured to stop thinking immediately on any user
16114  * input.  However, that gnuchess feature depends on the FIONREAD
16115  * ioctl, which does not work properly on some flavors of Unix.
16116  */
16117 void
16118 Attention (ChessProgramState *cps)
16119 {
16120 #if ATTENTION
16121     if (!cps->useSigint) return;
16122     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16123     switch (gameMode) {
16124       case MachinePlaysWhite:
16125       case MachinePlaysBlack:
16126       case TwoMachinesPlay:
16127       case IcsPlayingWhite:
16128       case IcsPlayingBlack:
16129       case AnalyzeMode:
16130       case AnalyzeFile:
16131         /* Skip if we know it isn't thinking */
16132         if (!cps->maybeThinking) return;
16133         if (appData.debugMode)
16134           fprintf(debugFP, "Interrupting %s\n", cps->which);
16135         InterruptChildProcess(cps->pr);
16136         cps->maybeThinking = FALSE;
16137         break;
16138       default:
16139         break;
16140     }
16141 #endif /*ATTENTION*/
16142 }
16143
16144 int
16145 CheckFlags ()
16146 {
16147     if (whiteTimeRemaining <= 0) {
16148         if (!whiteFlag) {
16149             whiteFlag = TRUE;
16150             if (appData.icsActive) {
16151                 if (appData.autoCallFlag &&
16152                     gameMode == IcsPlayingBlack && !blackFlag) {
16153                   SendToICS(ics_prefix);
16154                   SendToICS("flag\n");
16155                 }
16156             } else {
16157                 if (blackFlag) {
16158                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16159                 } else {
16160                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16161                     if (appData.autoCallFlag) {
16162                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16163                         return TRUE;
16164                     }
16165                 }
16166             }
16167         }
16168     }
16169     if (blackTimeRemaining <= 0) {
16170         if (!blackFlag) {
16171             blackFlag = TRUE;
16172             if (appData.icsActive) {
16173                 if (appData.autoCallFlag &&
16174                     gameMode == IcsPlayingWhite && !whiteFlag) {
16175                   SendToICS(ics_prefix);
16176                   SendToICS("flag\n");
16177                 }
16178             } else {
16179                 if (whiteFlag) {
16180                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16181                 } else {
16182                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16183                     if (appData.autoCallFlag) {
16184                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16185                         return TRUE;
16186                     }
16187                 }
16188             }
16189         }
16190     }
16191     return FALSE;
16192 }
16193
16194 void
16195 CheckTimeControl ()
16196 {
16197     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16198         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16199
16200     /*
16201      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16202      */
16203     if ( !WhiteOnMove(forwardMostMove) ) {
16204         /* White made time control */
16205         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16206         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16207         /* [HGM] time odds: correct new time quota for time odds! */
16208                                             / WhitePlayer()->timeOdds;
16209         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16210     } else {
16211         lastBlack -= blackTimeRemaining;
16212         /* Black made time control */
16213         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16214                                             / WhitePlayer()->other->timeOdds;
16215         lastWhite = whiteTimeRemaining;
16216     }
16217 }
16218
16219 void
16220 DisplayBothClocks ()
16221 {
16222     int wom = gameMode == EditPosition ?
16223       !blackPlaysFirst : WhiteOnMove(currentMove);
16224     DisplayWhiteClock(whiteTimeRemaining, wom);
16225     DisplayBlackClock(blackTimeRemaining, !wom);
16226 }
16227
16228
16229 /* Timekeeping seems to be a portability nightmare.  I think everyone
16230    has ftime(), but I'm really not sure, so I'm including some ifdefs
16231    to use other calls if you don't.  Clocks will be less accurate if
16232    you have neither ftime nor gettimeofday.
16233 */
16234
16235 /* VS 2008 requires the #include outside of the function */
16236 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16237 #include <sys/timeb.h>
16238 #endif
16239
16240 /* Get the current time as a TimeMark */
16241 void
16242 GetTimeMark (TimeMark *tm)
16243 {
16244 #if HAVE_GETTIMEOFDAY
16245
16246     struct timeval timeVal;
16247     struct timezone timeZone;
16248
16249     gettimeofday(&timeVal, &timeZone);
16250     tm->sec = (long) timeVal.tv_sec;
16251     tm->ms = (int) (timeVal.tv_usec / 1000L);
16252
16253 #else /*!HAVE_GETTIMEOFDAY*/
16254 #if HAVE_FTIME
16255
16256 // include <sys/timeb.h> / moved to just above start of function
16257     struct timeb timeB;
16258
16259     ftime(&timeB);
16260     tm->sec = (long) timeB.time;
16261     tm->ms = (int) timeB.millitm;
16262
16263 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16264     tm->sec = (long) time(NULL);
16265     tm->ms = 0;
16266 #endif
16267 #endif
16268 }
16269
16270 /* Return the difference in milliseconds between two
16271    time marks.  We assume the difference will fit in a long!
16272 */
16273 long
16274 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16275 {
16276     return 1000L*(tm2->sec - tm1->sec) +
16277            (long) (tm2->ms - tm1->ms);
16278 }
16279
16280
16281 /*
16282  * Code to manage the game clocks.
16283  *
16284  * In tournament play, black starts the clock and then white makes a move.
16285  * We give the human user a slight advantage if he is playing white---the
16286  * clocks don't run until he makes his first move, so it takes zero time.
16287  * Also, we don't account for network lag, so we could get out of sync
16288  * with GNU Chess's clock -- but then, referees are always right.
16289  */
16290
16291 static TimeMark tickStartTM;
16292 static long intendedTickLength;
16293
16294 long
16295 NextTickLength (long timeRemaining)
16296 {
16297     long nominalTickLength, nextTickLength;
16298
16299     if (timeRemaining > 0L && timeRemaining <= 10000L)
16300       nominalTickLength = 100L;
16301     else
16302       nominalTickLength = 1000L;
16303     nextTickLength = timeRemaining % nominalTickLength;
16304     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16305
16306     return nextTickLength;
16307 }
16308
16309 /* Adjust clock one minute up or down */
16310 void
16311 AdjustClock (Boolean which, int dir)
16312 {
16313     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16314     if(which) blackTimeRemaining += 60000*dir;
16315     else      whiteTimeRemaining += 60000*dir;
16316     DisplayBothClocks();
16317     adjustedClock = TRUE;
16318 }
16319
16320 /* Stop clocks and reset to a fresh time control */
16321 void
16322 ResetClocks ()
16323 {
16324     (void) StopClockTimer();
16325     if (appData.icsActive) {
16326         whiteTimeRemaining = blackTimeRemaining = 0;
16327     } else if (searchTime) {
16328         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16329         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16330     } else { /* [HGM] correct new time quote for time odds */
16331         whiteTC = blackTC = fullTimeControlString;
16332         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16333         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16334     }
16335     if (whiteFlag || blackFlag) {
16336         DisplayTitle("");
16337         whiteFlag = blackFlag = FALSE;
16338     }
16339     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16340     DisplayBothClocks();
16341     adjustedClock = FALSE;
16342 }
16343
16344 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16345
16346 /* Decrement running clock by amount of time that has passed */
16347 void
16348 DecrementClocks ()
16349 {
16350     long timeRemaining;
16351     long lastTickLength, fudge;
16352     TimeMark now;
16353
16354     if (!appData.clockMode) return;
16355     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16356
16357     GetTimeMark(&now);
16358
16359     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16360
16361     /* Fudge if we woke up a little too soon */
16362     fudge = intendedTickLength - lastTickLength;
16363     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16364
16365     if (WhiteOnMove(forwardMostMove)) {
16366         if(whiteNPS >= 0) lastTickLength = 0;
16367         timeRemaining = whiteTimeRemaining -= lastTickLength;
16368         if(timeRemaining < 0 && !appData.icsActive) {
16369             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16370             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16371                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16372                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16373             }
16374         }
16375         DisplayWhiteClock(whiteTimeRemaining - fudge,
16376                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16377     } else {
16378         if(blackNPS >= 0) lastTickLength = 0;
16379         timeRemaining = blackTimeRemaining -= lastTickLength;
16380         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16381             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16382             if(suddenDeath) {
16383                 blackStartMove = forwardMostMove;
16384                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16385             }
16386         }
16387         DisplayBlackClock(blackTimeRemaining - fudge,
16388                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16389     }
16390     if (CheckFlags()) return;
16391
16392     if(twoBoards) { // count down secondary board's clocks as well
16393         activePartnerTime -= lastTickLength;
16394         partnerUp = 1;
16395         if(activePartner == 'W')
16396             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16397         else
16398             DisplayBlackClock(activePartnerTime, TRUE);
16399         partnerUp = 0;
16400     }
16401
16402     tickStartTM = now;
16403     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16404     StartClockTimer(intendedTickLength);
16405
16406     /* if the time remaining has fallen below the alarm threshold, sound the
16407      * alarm. if the alarm has sounded and (due to a takeback or time control
16408      * with increment) the time remaining has increased to a level above the
16409      * threshold, reset the alarm so it can sound again.
16410      */
16411
16412     if (appData.icsActive && appData.icsAlarm) {
16413
16414         /* make sure we are dealing with the user's clock */
16415         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16416                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16417            )) return;
16418
16419         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16420             alarmSounded = FALSE;
16421         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16422             PlayAlarmSound();
16423             alarmSounded = TRUE;
16424         }
16425     }
16426 }
16427
16428
16429 /* A player has just moved, so stop the previously running
16430    clock and (if in clock mode) start the other one.
16431    We redisplay both clocks in case we're in ICS mode, because
16432    ICS gives us an update to both clocks after every move.
16433    Note that this routine is called *after* forwardMostMove
16434    is updated, so the last fractional tick must be subtracted
16435    from the color that is *not* on move now.
16436 */
16437 void
16438 SwitchClocks (int newMoveNr)
16439 {
16440     long lastTickLength;
16441     TimeMark now;
16442     int flagged = FALSE;
16443
16444     GetTimeMark(&now);
16445
16446     if (StopClockTimer() && appData.clockMode) {
16447         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16448         if (!WhiteOnMove(forwardMostMove)) {
16449             if(blackNPS >= 0) lastTickLength = 0;
16450             blackTimeRemaining -= lastTickLength;
16451            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16452 //         if(pvInfoList[forwardMostMove].time == -1)
16453                  pvInfoList[forwardMostMove].time =               // use GUI time
16454                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16455         } else {
16456            if(whiteNPS >= 0) lastTickLength = 0;
16457            whiteTimeRemaining -= lastTickLength;
16458            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16459 //         if(pvInfoList[forwardMostMove].time == -1)
16460                  pvInfoList[forwardMostMove].time =
16461                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16462         }
16463         flagged = CheckFlags();
16464     }
16465     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16466     CheckTimeControl();
16467
16468     if (flagged || !appData.clockMode) return;
16469
16470     switch (gameMode) {
16471       case MachinePlaysBlack:
16472       case MachinePlaysWhite:
16473       case BeginningOfGame:
16474         if (pausing) return;
16475         break;
16476
16477       case EditGame:
16478       case PlayFromGameFile:
16479       case IcsExamining:
16480         return;
16481
16482       default:
16483         break;
16484     }
16485
16486     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16487         if(WhiteOnMove(forwardMostMove))
16488              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16489         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16490     }
16491
16492     tickStartTM = now;
16493     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16494       whiteTimeRemaining : blackTimeRemaining);
16495     StartClockTimer(intendedTickLength);
16496 }
16497
16498
16499 /* Stop both clocks */
16500 void
16501 StopClocks ()
16502 {
16503     long lastTickLength;
16504     TimeMark now;
16505
16506     if (!StopClockTimer()) return;
16507     if (!appData.clockMode) return;
16508
16509     GetTimeMark(&now);
16510
16511     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16512     if (WhiteOnMove(forwardMostMove)) {
16513         if(whiteNPS >= 0) lastTickLength = 0;
16514         whiteTimeRemaining -= lastTickLength;
16515         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16516     } else {
16517         if(blackNPS >= 0) lastTickLength = 0;
16518         blackTimeRemaining -= lastTickLength;
16519         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16520     }
16521     CheckFlags();
16522 }
16523
16524 /* Start clock of player on move.  Time may have been reset, so
16525    if clock is already running, stop and restart it. */
16526 void
16527 StartClocks ()
16528 {
16529     (void) StopClockTimer(); /* in case it was running already */
16530     DisplayBothClocks();
16531     if (CheckFlags()) return;
16532
16533     if (!appData.clockMode) return;
16534     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16535
16536     GetTimeMark(&tickStartTM);
16537     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16538       whiteTimeRemaining : blackTimeRemaining);
16539
16540    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16541     whiteNPS = blackNPS = -1;
16542     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16543        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16544         whiteNPS = first.nps;
16545     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16546        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16547         blackNPS = first.nps;
16548     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16549         whiteNPS = second.nps;
16550     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16551         blackNPS = second.nps;
16552     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16553
16554     StartClockTimer(intendedTickLength);
16555 }
16556
16557 char *
16558 TimeString (long ms)
16559 {
16560     long second, minute, hour, day;
16561     char *sign = "";
16562     static char buf[32];
16563
16564     if (ms > 0 && ms <= 9900) {
16565       /* convert milliseconds to tenths, rounding up */
16566       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16567
16568       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16569       return buf;
16570     }
16571
16572     /* convert milliseconds to seconds, rounding up */
16573     /* use floating point to avoid strangeness of integer division
16574        with negative dividends on many machines */
16575     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16576
16577     if (second < 0) {
16578         sign = "-";
16579         second = -second;
16580     }
16581
16582     day = second / (60 * 60 * 24);
16583     second = second % (60 * 60 * 24);
16584     hour = second / (60 * 60);
16585     second = second % (60 * 60);
16586     minute = second / 60;
16587     second = second % 60;
16588
16589     if (day > 0)
16590       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16591               sign, day, hour, minute, second);
16592     else if (hour > 0)
16593       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16594     else
16595       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16596
16597     return buf;
16598 }
16599
16600
16601 /*
16602  * This is necessary because some C libraries aren't ANSI C compliant yet.
16603  */
16604 char *
16605 StrStr (char *string, char *match)
16606 {
16607     int i, length;
16608
16609     length = strlen(match);
16610
16611     for (i = strlen(string) - length; i >= 0; i--, string++)
16612       if (!strncmp(match, string, length))
16613         return string;
16614
16615     return NULL;
16616 }
16617
16618 char *
16619 StrCaseStr (char *string, char *match)
16620 {
16621     int i, j, length;
16622
16623     length = strlen(match);
16624
16625     for (i = strlen(string) - length; i >= 0; i--, string++) {
16626         for (j = 0; j < length; j++) {
16627             if (ToLower(match[j]) != ToLower(string[j]))
16628               break;
16629         }
16630         if (j == length) return string;
16631     }
16632
16633     return NULL;
16634 }
16635
16636 #ifndef _amigados
16637 int
16638 StrCaseCmp (char *s1, char *s2)
16639 {
16640     char c1, c2;
16641
16642     for (;;) {
16643         c1 = ToLower(*s1++);
16644         c2 = ToLower(*s2++);
16645         if (c1 > c2) return 1;
16646         if (c1 < c2) return -1;
16647         if (c1 == NULLCHAR) return 0;
16648     }
16649 }
16650
16651
16652 int
16653 ToLower (int c)
16654 {
16655     return isupper(c) ? tolower(c) : c;
16656 }
16657
16658
16659 int
16660 ToUpper (int c)
16661 {
16662     return islower(c) ? toupper(c) : c;
16663 }
16664 #endif /* !_amigados    */
16665
16666 char *
16667 StrSave (char *s)
16668 {
16669   char *ret;
16670
16671   if ((ret = (char *) malloc(strlen(s) + 1)))
16672     {
16673       safeStrCpy(ret, s, strlen(s)+1);
16674     }
16675   return ret;
16676 }
16677
16678 char *
16679 StrSavePtr (char *s, char **savePtr)
16680 {
16681     if (*savePtr) {
16682         free(*savePtr);
16683     }
16684     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16685       safeStrCpy(*savePtr, s, strlen(s)+1);
16686     }
16687     return(*savePtr);
16688 }
16689
16690 char *
16691 PGNDate ()
16692 {
16693     time_t clock;
16694     struct tm *tm;
16695     char buf[MSG_SIZ];
16696
16697     clock = time((time_t *)NULL);
16698     tm = localtime(&clock);
16699     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16700             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16701     return StrSave(buf);
16702 }
16703
16704
16705 char *
16706 PositionToFEN (int move, char *overrideCastling)
16707 {
16708     int i, j, fromX, fromY, toX, toY;
16709     int whiteToPlay;
16710     char buf[MSG_SIZ];
16711     char *p, *q;
16712     int emptycount;
16713     ChessSquare piece;
16714
16715     whiteToPlay = (gameMode == EditPosition) ?
16716       !blackPlaysFirst : (move % 2 == 0);
16717     p = buf;
16718
16719     /* Piece placement data */
16720     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16721         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16722         emptycount = 0;
16723         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16724             if (boards[move][i][j] == EmptySquare) {
16725                 emptycount++;
16726             } else { ChessSquare piece = boards[move][i][j];
16727                 if (emptycount > 0) {
16728                     if(emptycount<10) /* [HGM] can be >= 10 */
16729                         *p++ = '0' + emptycount;
16730                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16731                     emptycount = 0;
16732                 }
16733                 if(PieceToChar(piece) == '+') {
16734                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16735                     *p++ = '+';
16736                     piece = (ChessSquare)(DEMOTED piece);
16737                 }
16738                 *p++ = PieceToChar(piece);
16739                 if(p[-1] == '~') {
16740                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16741                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16742                     *p++ = '~';
16743                 }
16744             }
16745         }
16746         if (emptycount > 0) {
16747             if(emptycount<10) /* [HGM] can be >= 10 */
16748                 *p++ = '0' + emptycount;
16749             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16750             emptycount = 0;
16751         }
16752         *p++ = '/';
16753     }
16754     *(p - 1) = ' ';
16755
16756     /* [HGM] print Crazyhouse or Shogi holdings */
16757     if( gameInfo.holdingsWidth ) {
16758         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16759         q = p;
16760         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16761             piece = boards[move][i][BOARD_WIDTH-1];
16762             if( piece != EmptySquare )
16763               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16764                   *p++ = PieceToChar(piece);
16765         }
16766         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16767             piece = boards[move][BOARD_HEIGHT-i-1][0];
16768             if( piece != EmptySquare )
16769               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16770                   *p++ = PieceToChar(piece);
16771         }
16772
16773         if( q == p ) *p++ = '-';
16774         *p++ = ']';
16775         *p++ = ' ';
16776     }
16777
16778     /* Active color */
16779     *p++ = whiteToPlay ? 'w' : 'b';
16780     *p++ = ' ';
16781
16782   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16783     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16784   } else {
16785   if(nrCastlingRights) {
16786      q = p;
16787      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16788        /* [HGM] write directly from rights */
16789            if(boards[move][CASTLING][2] != NoRights &&
16790               boards[move][CASTLING][0] != NoRights   )
16791                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16792            if(boards[move][CASTLING][2] != NoRights &&
16793               boards[move][CASTLING][1] != NoRights   )
16794                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16795            if(boards[move][CASTLING][5] != NoRights &&
16796               boards[move][CASTLING][3] != NoRights   )
16797                 *p++ = boards[move][CASTLING][3] + AAA;
16798            if(boards[move][CASTLING][5] != NoRights &&
16799               boards[move][CASTLING][4] != NoRights   )
16800                 *p++ = boards[move][CASTLING][4] + AAA;
16801      } else {
16802
16803         /* [HGM] write true castling rights */
16804         if( nrCastlingRights == 6 ) {
16805             int q, k=0;
16806             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16807                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16808             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16809                  boards[move][CASTLING][2] != NoRights  );
16810             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16811                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16812                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16813                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16814                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16815             }
16816             if(q) *p++ = 'Q';
16817             k = 0;
16818             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16819                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16820             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16821                  boards[move][CASTLING][5] != NoRights  );
16822             if(gameInfo.variant == VariantSChess) {
16823                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16824                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16825                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16826                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16827             }
16828             if(q) *p++ = 'q';
16829         }
16830      }
16831      if (q == p) *p++ = '-'; /* No castling rights */
16832      *p++ = ' ';
16833   }
16834
16835   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16836      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16837     /* En passant target square */
16838     if (move > backwardMostMove) {
16839         fromX = moveList[move - 1][0] - AAA;
16840         fromY = moveList[move - 1][1] - ONE;
16841         toX = moveList[move - 1][2] - AAA;
16842         toY = moveList[move - 1][3] - ONE;
16843         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16844             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16845             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16846             fromX == toX) {
16847             /* 2-square pawn move just happened */
16848             *p++ = toX + AAA;
16849             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16850         } else {
16851             *p++ = '-';
16852         }
16853     } else if(move == backwardMostMove) {
16854         // [HGM] perhaps we should always do it like this, and forget the above?
16855         if((signed char)boards[move][EP_STATUS] >= 0) {
16856             *p++ = boards[move][EP_STATUS] + AAA;
16857             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16858         } else {
16859             *p++ = '-';
16860         }
16861     } else {
16862         *p++ = '-';
16863     }
16864     *p++ = ' ';
16865   }
16866   }
16867
16868     /* [HGM] find reversible plies */
16869     {   int i = 0, j=move;
16870
16871         if (appData.debugMode) { int k;
16872             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16873             for(k=backwardMostMove; k<=forwardMostMove; k++)
16874                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16875
16876         }
16877
16878         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16879         if( j == backwardMostMove ) i += initialRulePlies;
16880         sprintf(p, "%d ", i);
16881         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16882     }
16883     /* Fullmove number */
16884     sprintf(p, "%d", (move / 2) + 1);
16885
16886     return StrSave(buf);
16887 }
16888
16889 Boolean
16890 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16891 {
16892     int i, j;
16893     char *p, c;
16894     int emptycount, virgin[BOARD_FILES];
16895     ChessSquare piece;
16896
16897     p = fen;
16898
16899     /* [HGM] by default clear Crazyhouse holdings, if present */
16900     if(gameInfo.holdingsWidth) {
16901        for(i=0; i<BOARD_HEIGHT; i++) {
16902            board[i][0]             = EmptySquare; /* black holdings */
16903            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16904            board[i][1]             = (ChessSquare) 0; /* black counts */
16905            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16906        }
16907     }
16908
16909     /* Piece placement data */
16910     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16911         j = 0;
16912         for (;;) {
16913             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16914                 if (*p == '/') p++;
16915                 emptycount = gameInfo.boardWidth - j;
16916                 while (emptycount--)
16917                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16918                 break;
16919 #if(BOARD_FILES >= 10)
16920             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16921                 p++; emptycount=10;
16922                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16923                 while (emptycount--)
16924                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16925 #endif
16926             } else if (isdigit(*p)) {
16927                 emptycount = *p++ - '0';
16928                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16929                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16930                 while (emptycount--)
16931                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16932             } else if (*p == '+' || isalpha(*p)) {
16933                 if (j >= gameInfo.boardWidth) return FALSE;
16934                 if(*p=='+') {
16935                     piece = CharToPiece(*++p);
16936                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16937                     piece = (ChessSquare) (PROMOTED piece ); p++;
16938                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16939                 } else piece = CharToPiece(*p++);
16940
16941                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16942                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16943                     piece = (ChessSquare) (PROMOTED piece);
16944                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16945                     p++;
16946                 }
16947                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16948             } else {
16949                 return FALSE;
16950             }
16951         }
16952     }
16953     while (*p == '/' || *p == ' ') p++;
16954
16955     /* [HGM] look for Crazyhouse holdings here */
16956     while(*p==' ') p++;
16957     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16958         if(*p == '[') p++;
16959         if(*p == '-' ) p++; /* empty holdings */ else {
16960             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16961             /* if we would allow FEN reading to set board size, we would   */
16962             /* have to add holdings and shift the board read so far here   */
16963             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16964                 p++;
16965                 if((int) piece >= (int) BlackPawn ) {
16966                     i = (int)piece - (int)BlackPawn;
16967                     i = PieceToNumber((ChessSquare)i);
16968                     if( i >= gameInfo.holdingsSize ) return FALSE;
16969                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16970                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16971                 } else {
16972                     i = (int)piece - (int)WhitePawn;
16973                     i = PieceToNumber((ChessSquare)i);
16974                     if( i >= gameInfo.holdingsSize ) return FALSE;
16975                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16976                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16977                 }
16978             }
16979         }
16980         if(*p == ']') p++;
16981     }
16982
16983     while(*p == ' ') p++;
16984
16985     /* Active color */
16986     c = *p++;
16987     if(appData.colorNickNames) {
16988       if( c == appData.colorNickNames[0] ) c = 'w'; else
16989       if( c == appData.colorNickNames[1] ) c = 'b';
16990     }
16991     switch (c) {
16992       case 'w':
16993         *blackPlaysFirst = FALSE;
16994         break;
16995       case 'b':
16996         *blackPlaysFirst = TRUE;
16997         break;
16998       default:
16999         return FALSE;
17000     }
17001
17002     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17003     /* return the extra info in global variiables             */
17004
17005     /* set defaults in case FEN is incomplete */
17006     board[EP_STATUS] = EP_UNKNOWN;
17007     for(i=0; i<nrCastlingRights; i++ ) {
17008         board[CASTLING][i] =
17009             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17010     }   /* assume possible unless obviously impossible */
17011     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17012     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17013     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17014                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17015     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17016     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17017     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17018                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17019     FENrulePlies = 0;
17020
17021     while(*p==' ') p++;
17022     if(nrCastlingRights) {
17023       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17024       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17025           /* castling indicator present, so default becomes no castlings */
17026           for(i=0; i<nrCastlingRights; i++ ) {
17027                  board[CASTLING][i] = NoRights;
17028           }
17029       }
17030       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17031              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17032              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17033              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17034         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17035
17036         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17037             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17038             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17039         }
17040         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17041             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17042         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17043                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17044         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17045                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17046         switch(c) {
17047           case'K':
17048               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17049               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17050               board[CASTLING][2] = whiteKingFile;
17051               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17052               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17053               break;
17054           case'Q':
17055               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17056               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17057               board[CASTLING][2] = whiteKingFile;
17058               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17059               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17060               break;
17061           case'k':
17062               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17063               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17064               board[CASTLING][5] = blackKingFile;
17065               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17066               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17067               break;
17068           case'q':
17069               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17070               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17071               board[CASTLING][5] = blackKingFile;
17072               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17073               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17074           case '-':
17075               break;
17076           default: /* FRC castlings */
17077               if(c >= 'a') { /* black rights */
17078                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17079                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17080                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17081                   if(i == BOARD_RGHT) break;
17082                   board[CASTLING][5] = i;
17083                   c -= AAA;
17084                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17085                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17086                   if(c > i)
17087                       board[CASTLING][3] = c;
17088                   else
17089                       board[CASTLING][4] = c;
17090               } else { /* white rights */
17091                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17092                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17093                     if(board[0][i] == WhiteKing) break;
17094                   if(i == BOARD_RGHT) break;
17095                   board[CASTLING][2] = i;
17096                   c -= AAA - 'a' + 'A';
17097                   if(board[0][c] >= WhiteKing) break;
17098                   if(c > i)
17099                       board[CASTLING][0] = c;
17100                   else
17101                       board[CASTLING][1] = c;
17102               }
17103         }
17104       }
17105       for(i=0; i<nrCastlingRights; i++)
17106         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17107       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17108     if (appData.debugMode) {
17109         fprintf(debugFP, "FEN castling rights:");
17110         for(i=0; i<nrCastlingRights; i++)
17111         fprintf(debugFP, " %d", board[CASTLING][i]);
17112         fprintf(debugFP, "\n");
17113     }
17114
17115       while(*p==' ') p++;
17116     }
17117
17118     /* read e.p. field in games that know e.p. capture */
17119     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17120        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17121       if(*p=='-') {
17122         p++; board[EP_STATUS] = EP_NONE;
17123       } else {
17124          char c = *p++ - AAA;
17125
17126          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17127          if(*p >= '0' && *p <='9') p++;
17128          board[EP_STATUS] = c;
17129       }
17130     }
17131
17132
17133     if(sscanf(p, "%d", &i) == 1) {
17134         FENrulePlies = i; /* 50-move ply counter */
17135         /* (The move number is still ignored)    */
17136     }
17137
17138     return TRUE;
17139 }
17140
17141 void
17142 EditPositionPasteFEN (char *fen)
17143 {
17144   if (fen != NULL) {
17145     Board initial_position;
17146
17147     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17148       DisplayError(_("Bad FEN position in clipboard"), 0);
17149       return ;
17150     } else {
17151       int savedBlackPlaysFirst = blackPlaysFirst;
17152       EditPositionEvent();
17153       blackPlaysFirst = savedBlackPlaysFirst;
17154       CopyBoard(boards[0], initial_position);
17155       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17156       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17157       DisplayBothClocks();
17158       DrawPosition(FALSE, boards[currentMove]);
17159     }
17160   }
17161 }
17162
17163 static char cseq[12] = "\\   ";
17164
17165 Boolean
17166 set_cont_sequence (char *new_seq)
17167 {
17168     int len;
17169     Boolean ret;
17170
17171     // handle bad attempts to set the sequence
17172         if (!new_seq)
17173                 return 0; // acceptable error - no debug
17174
17175     len = strlen(new_seq);
17176     ret = (len > 0) && (len < sizeof(cseq));
17177     if (ret)
17178       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17179     else if (appData.debugMode)
17180       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17181     return ret;
17182 }
17183
17184 /*
17185     reformat a source message so words don't cross the width boundary.  internal
17186     newlines are not removed.  returns the wrapped size (no null character unless
17187     included in source message).  If dest is NULL, only calculate the size required
17188     for the dest buffer.  lp argument indicats line position upon entry, and it's
17189     passed back upon exit.
17190 */
17191 int
17192 wrap (char *dest, char *src, int count, int width, int *lp)
17193 {
17194     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17195
17196     cseq_len = strlen(cseq);
17197     old_line = line = *lp;
17198     ansi = len = clen = 0;
17199
17200     for (i=0; i < count; i++)
17201     {
17202         if (src[i] == '\033')
17203             ansi = 1;
17204
17205         // if we hit the width, back up
17206         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17207         {
17208             // store i & len in case the word is too long
17209             old_i = i, old_len = len;
17210
17211             // find the end of the last word
17212             while (i && src[i] != ' ' && src[i] != '\n')
17213             {
17214                 i--;
17215                 len--;
17216             }
17217
17218             // word too long?  restore i & len before splitting it
17219             if ((old_i-i+clen) >= width)
17220             {
17221                 i = old_i;
17222                 len = old_len;
17223             }
17224
17225             // extra space?
17226             if (i && src[i-1] == ' ')
17227                 len--;
17228
17229             if (src[i] != ' ' && src[i] != '\n')
17230             {
17231                 i--;
17232                 if (len)
17233                     len--;
17234             }
17235
17236             // now append the newline and continuation sequence
17237             if (dest)
17238                 dest[len] = '\n';
17239             len++;
17240             if (dest)
17241                 strncpy(dest+len, cseq, cseq_len);
17242             len += cseq_len;
17243             line = cseq_len;
17244             clen = cseq_len;
17245             continue;
17246         }
17247
17248         if (dest)
17249             dest[len] = src[i];
17250         len++;
17251         if (!ansi)
17252             line++;
17253         if (src[i] == '\n')
17254             line = 0;
17255         if (src[i] == 'm')
17256             ansi = 0;
17257     }
17258     if (dest && appData.debugMode)
17259     {
17260         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17261             count, width, line, len, *lp);
17262         show_bytes(debugFP, src, count);
17263         fprintf(debugFP, "\ndest: ");
17264         show_bytes(debugFP, dest, len);
17265         fprintf(debugFP, "\n");
17266     }
17267     *lp = dest ? line : old_line;
17268
17269     return len;
17270 }
17271
17272 // [HGM] vari: routines for shelving variations
17273 Boolean modeRestore = FALSE;
17274
17275 void
17276 PushInner (int firstMove, int lastMove)
17277 {
17278         int i, j, nrMoves = lastMove - firstMove;
17279
17280         // push current tail of game on stack
17281         savedResult[storedGames] = gameInfo.result;
17282         savedDetails[storedGames] = gameInfo.resultDetails;
17283         gameInfo.resultDetails = NULL;
17284         savedFirst[storedGames] = firstMove;
17285         savedLast [storedGames] = lastMove;
17286         savedFramePtr[storedGames] = framePtr;
17287         framePtr -= nrMoves; // reserve space for the boards
17288         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17289             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17290             for(j=0; j<MOVE_LEN; j++)
17291                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17292             for(j=0; j<2*MOVE_LEN; j++)
17293                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17294             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17295             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17296             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17297             pvInfoList[firstMove+i-1].depth = 0;
17298             commentList[framePtr+i] = commentList[firstMove+i];
17299             commentList[firstMove+i] = NULL;
17300         }
17301
17302         storedGames++;
17303         forwardMostMove = firstMove; // truncate game so we can start variation
17304 }
17305
17306 void
17307 PushTail (int firstMove, int lastMove)
17308 {
17309         if(appData.icsActive) { // only in local mode
17310                 forwardMostMove = currentMove; // mimic old ICS behavior
17311                 return;
17312         }
17313         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17314
17315         PushInner(firstMove, lastMove);
17316         if(storedGames == 1) GreyRevert(FALSE);
17317         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17318 }
17319
17320 void
17321 PopInner (Boolean annotate)
17322 {
17323         int i, j, nrMoves;
17324         char buf[8000], moveBuf[20];
17325
17326         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17327         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17328         nrMoves = savedLast[storedGames] - currentMove;
17329         if(annotate) {
17330                 int cnt = 10;
17331                 if(!WhiteOnMove(currentMove))
17332                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17333                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17334                 for(i=currentMove; i<forwardMostMove; i++) {
17335                         if(WhiteOnMove(i))
17336                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17337                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17338                         strcat(buf, moveBuf);
17339                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17340                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17341                 }
17342                 strcat(buf, ")");
17343         }
17344         for(i=1; i<=nrMoves; i++) { // copy last variation back
17345             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17346             for(j=0; j<MOVE_LEN; j++)
17347                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17348             for(j=0; j<2*MOVE_LEN; j++)
17349                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17350             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17351             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17352             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17353             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17354             commentList[currentMove+i] = commentList[framePtr+i];
17355             commentList[framePtr+i] = NULL;
17356         }
17357         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17358         framePtr = savedFramePtr[storedGames];
17359         gameInfo.result = savedResult[storedGames];
17360         if(gameInfo.resultDetails != NULL) {
17361             free(gameInfo.resultDetails);
17362       }
17363         gameInfo.resultDetails = savedDetails[storedGames];
17364         forwardMostMove = currentMove + nrMoves;
17365 }
17366
17367 Boolean
17368 PopTail (Boolean annotate)
17369 {
17370         if(appData.icsActive) return FALSE; // only in local mode
17371         if(!storedGames) return FALSE; // sanity
17372         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17373
17374         PopInner(annotate);
17375         if(currentMove < forwardMostMove) ForwardEvent(); else
17376         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17377
17378         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17379         return TRUE;
17380 }
17381
17382 void
17383 CleanupTail ()
17384 {       // remove all shelved variations
17385         int i;
17386         for(i=0; i<storedGames; i++) {
17387             if(savedDetails[i])
17388                 free(savedDetails[i]);
17389             savedDetails[i] = NULL;
17390         }
17391         for(i=framePtr; i<MAX_MOVES; i++) {
17392                 if(commentList[i]) free(commentList[i]);
17393                 commentList[i] = NULL;
17394         }
17395         framePtr = MAX_MOVES-1;
17396         storedGames = 0;
17397 }
17398
17399 void
17400 LoadVariation (int index, char *text)
17401 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17402         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17403         int level = 0, move;
17404
17405         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17406         // first find outermost bracketing variation
17407         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17408             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17409                 if(*p == '{') wait = '}'; else
17410                 if(*p == '[') wait = ']'; else
17411                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17412                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17413             }
17414             if(*p == wait) wait = NULLCHAR; // closing ]} found
17415             p++;
17416         }
17417         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17418         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17419         end[1] = NULLCHAR; // clip off comment beyond variation
17420         ToNrEvent(currentMove-1);
17421         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17422         // kludge: use ParsePV() to append variation to game
17423         move = currentMove;
17424         ParsePV(start, TRUE, TRUE);
17425         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17426         ClearPremoveHighlights();
17427         CommentPopDown();
17428         ToNrEvent(currentMove+1);
17429 }
17430