Redo Eval Graph drawing 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 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2515 {
2516         char buf[MSG_SIZ], *ext = "";
2517         VariantClass v = StringToVariant(type);
2518         if(strstr(type, "wild")) {
2519             ext = type + 4; // append wild number
2520             if(v == VariantFischeRandom) type = "chess960"; else
2521             if(v == VariantLoadable) type = "setup"; else
2522             type = VariantName(v);
2523         }
2524         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2525         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2526             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2527             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2528             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2529             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2530             seekNrList[nrOfSeekAds] = nr;
2531             zList[nrOfSeekAds] = 0;
2532             seekAdList[nrOfSeekAds++] = StrSave(buf);
2533             if(plot) PlotSeekAd(nrOfSeekAds-1);
2534         }
2535 }
2536
2537 void
2538 EraseSeekDot (int i)
2539 {
2540     int x = xList[i], y = yList[i], d=squareSize/4, k;
2541     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2542     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2543     // now replot every dot that overlapped
2544     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2545         int xx = xList[k], yy = yList[k];
2546         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2547             DrawSeekDot(xx, yy, colorList[k]);
2548     }
2549 }
2550
2551 void
2552 RemoveSeekAd (int nr)
2553 {
2554         int i;
2555         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2556             EraseSeekDot(i);
2557             if(seekAdList[i]) free(seekAdList[i]);
2558             seekAdList[i] = seekAdList[--nrOfSeekAds];
2559             seekNrList[i] = seekNrList[nrOfSeekAds];
2560             ratingList[i] = ratingList[nrOfSeekAds];
2561             colorList[i]  = colorList[nrOfSeekAds];
2562             tcList[i] = tcList[nrOfSeekAds];
2563             xList[i]  = xList[nrOfSeekAds];
2564             yList[i]  = yList[nrOfSeekAds];
2565             zList[i]  = zList[nrOfSeekAds];
2566             seekAdList[nrOfSeekAds] = NULL;
2567             break;
2568         }
2569 }
2570
2571 Boolean
2572 MatchSoughtLine (char *line)
2573 {
2574     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2575     int nr, base, inc, u=0; char dummy;
2576
2577     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2578        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2579        (u=1) &&
2580        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2581         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2582         // match: compact and save the line
2583         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2584         return TRUE;
2585     }
2586     return FALSE;
2587 }
2588
2589 int
2590 DrawSeekGraph ()
2591 {
2592     int i;
2593     if(!seekGraphUp) return FALSE;
2594     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2595     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2596
2597     DrawSeekBackground(0, 0, w, h);
2598     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2599     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2600     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2601         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2602         yy = h-1-yy;
2603         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2604         if(i%500 == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2608         }
2609     }
2610     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2611     for(i=1; i<100; i+=(i<10?1:5)) {
2612         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2613         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2614         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2615             char buf[MSG_SIZ];
2616             snprintf(buf, MSG_SIZ, "%d", i);
2617             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2618         }
2619     }
2620     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2621     return TRUE;
2622 }
2623
2624 int
2625 SeekGraphClick (ClickType click, int x, int y, int moving)
2626 {
2627     static int lastDown = 0, displayed = 0, lastSecond;
2628     if(y < 0) return FALSE;
2629     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2630         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2631         if(!seekGraphUp) return FALSE;
2632         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2633         DrawPosition(TRUE, NULL);
2634         return TRUE;
2635     }
2636     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2637         if(click == Release || moving) return FALSE;
2638         nrOfSeekAds = 0;
2639         soughtPending = TRUE;
2640         SendToICS(ics_prefix);
2641         SendToICS("sought\n"); // should this be "sought all"?
2642     } else { // issue challenge based on clicked ad
2643         int dist = 10000; int i, closest = 0, second = 0;
2644         for(i=0; i<nrOfSeekAds; i++) {
2645             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2646             if(d < dist) { dist = d; closest = i; }
2647             second += (d - zList[i] < 120); // count in-range ads
2648             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2649         }
2650         if(dist < 120) {
2651             char buf[MSG_SIZ];
2652             second = (second > 1);
2653             if(displayed != closest || second != lastSecond) {
2654                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2655                 lastSecond = second; displayed = closest;
2656             }
2657             if(click == Press) {
2658                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2659                 lastDown = closest;
2660                 return TRUE;
2661             } // on press 'hit', only show info
2662             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2663             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2664             SendToICS(ics_prefix);
2665             SendToICS(buf);
2666             return TRUE; // let incoming board of started game pop down the graph
2667         } else if(click == Release) { // release 'miss' is ignored
2668             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2669             if(moving == 2) { // right up-click
2670                 nrOfSeekAds = 0; // refresh graph
2671                 soughtPending = TRUE;
2672                 SendToICS(ics_prefix);
2673                 SendToICS("sought\n"); // should this be "sought all"?
2674             }
2675             return TRUE;
2676         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2677         // press miss or release hit 'pop down' seek graph
2678         seekGraphUp = FALSE;
2679         DrawPosition(TRUE, NULL);
2680     }
2681     return TRUE;
2682 }
2683
2684 void
2685 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2686 {
2687 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2688 #define STARTED_NONE 0
2689 #define STARTED_MOVES 1
2690 #define STARTED_BOARD 2
2691 #define STARTED_OBSERVE 3
2692 #define STARTED_HOLDINGS 4
2693 #define STARTED_CHATTER 5
2694 #define STARTED_COMMENT 6
2695 #define STARTED_MOVES_NOHIDE 7
2696
2697     static int started = STARTED_NONE;
2698     static char parse[20000];
2699     static int parse_pos = 0;
2700     static char buf[BUF_SIZE + 1];
2701     static int firstTime = TRUE, intfSet = FALSE;
2702     static ColorClass prevColor = ColorNormal;
2703     static int savingComment = FALSE;
2704     static int cmatch = 0; // continuation sequence match
2705     char *bp;
2706     char str[MSG_SIZ];
2707     int i, oldi;
2708     int buf_len;
2709     int next_out;
2710     int tkind;
2711     int backup;    /* [DM] For zippy color lines */
2712     char *p;
2713     char talker[MSG_SIZ]; // [HGM] chat
2714     int channel;
2715
2716     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2717
2718     if (appData.debugMode) {
2719       if (!error) {
2720         fprintf(debugFP, "<ICS: ");
2721         show_bytes(debugFP, data, count);
2722         fprintf(debugFP, "\n");
2723       }
2724     }
2725
2726     if (appData.debugMode) { int f = forwardMostMove;
2727         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2728                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2729                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2730     }
2731     if (count > 0) {
2732         /* If last read ended with a partial line that we couldn't parse,
2733            prepend it to the new read and try again. */
2734         if (leftover_len > 0) {
2735             for (i=0; i<leftover_len; i++)
2736               buf[i] = buf[leftover_start + i];
2737         }
2738
2739     /* copy new characters into the buffer */
2740     bp = buf + leftover_len;
2741     buf_len=leftover_len;
2742     for (i=0; i<count; i++)
2743     {
2744         // ignore these
2745         if (data[i] == '\r')
2746             continue;
2747
2748         // join lines split by ICS?
2749         if (!appData.noJoin)
2750         {
2751             /*
2752                 Joining just consists of finding matches against the
2753                 continuation sequence, and discarding that sequence
2754                 if found instead of copying it.  So, until a match
2755                 fails, there's nothing to do since it might be the
2756                 complete sequence, and thus, something we don't want
2757                 copied.
2758             */
2759             if (data[i] == cont_seq[cmatch])
2760             {
2761                 cmatch++;
2762                 if (cmatch == strlen(cont_seq))
2763                 {
2764                     cmatch = 0; // complete match.  just reset the counter
2765
2766                     /*
2767                         it's possible for the ICS to not include the space
2768                         at the end of the last word, making our [correct]
2769                         join operation fuse two separate words.  the server
2770                         does this when the space occurs at the width setting.
2771                     */
2772                     if (!buf_len || buf[buf_len-1] != ' ')
2773                     {
2774                         *bp++ = ' ';
2775                         buf_len++;
2776                     }
2777                 }
2778                 continue;
2779             }
2780             else if (cmatch)
2781             {
2782                 /*
2783                     match failed, so we have to copy what matched before
2784                     falling through and copying this character.  In reality,
2785                     this will only ever be just the newline character, but
2786                     it doesn't hurt to be precise.
2787                 */
2788                 strncpy(bp, cont_seq, cmatch);
2789                 bp += cmatch;
2790                 buf_len += cmatch;
2791                 cmatch = 0;
2792             }
2793         }
2794
2795         // copy this char
2796         *bp++ = data[i];
2797         buf_len++;
2798     }
2799
2800         buf[buf_len] = NULLCHAR;
2801 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2802         next_out = 0;
2803         leftover_start = 0;
2804
2805         i = 0;
2806         while (i < buf_len) {
2807             /* Deal with part of the TELNET option negotiation
2808                protocol.  We refuse to do anything beyond the
2809                defaults, except that we allow the WILL ECHO option,
2810                which ICS uses to turn off password echoing when we are
2811                directly connected to it.  We reject this option
2812                if localLineEditing mode is on (always on in xboard)
2813                and we are talking to port 23, which might be a real
2814                telnet server that will try to keep WILL ECHO on permanently.
2815              */
2816             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2817                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2818                 unsigned char option;
2819                 oldi = i;
2820                 switch ((unsigned char) buf[++i]) {
2821                   case TN_WILL:
2822                     if (appData.debugMode)
2823                       fprintf(debugFP, "\n<WILL ");
2824                     switch (option = (unsigned char) buf[++i]) {
2825                       case TN_ECHO:
2826                         if (appData.debugMode)
2827                           fprintf(debugFP, "ECHO ");
2828                         /* Reply only if this is a change, according
2829                            to the protocol rules. */
2830                         if (remoteEchoOption) break;
2831                         if (appData.localLineEditing &&
2832                             atoi(appData.icsPort) == TN_PORT) {
2833                             TelnetRequest(TN_DONT, TN_ECHO);
2834                         } else {
2835                             EchoOff();
2836                             TelnetRequest(TN_DO, TN_ECHO);
2837                             remoteEchoOption = TRUE;
2838                         }
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", option);
2843                         /* Whatever this is, we don't want it. */
2844                         TelnetRequest(TN_DONT, option);
2845                         break;
2846                     }
2847                     break;
2848                   case TN_WONT:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<WONT ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       case TN_ECHO:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "ECHO ");
2855                         /* Reply only if this is a change, according
2856                            to the protocol rules. */
2857                         if (!remoteEchoOption) break;
2858                         EchoOn();
2859                         TelnetRequest(TN_DONT, TN_ECHO);
2860                         remoteEchoOption = FALSE;
2861                         break;
2862                       default:
2863                         if (appData.debugMode)
2864                           fprintf(debugFP, "%d ", (unsigned char) option);
2865                         /* Whatever this is, it must already be turned
2866                            off, because we never agree to turn on
2867                            anything non-default, so according to the
2868                            protocol rules, we don't reply. */
2869                         break;
2870                     }
2871                     break;
2872                   case TN_DO:
2873                     if (appData.debugMode)
2874                       fprintf(debugFP, "\n<DO ");
2875                     switch (option = (unsigned char) buf[++i]) {
2876                       default:
2877                         /* Whatever this is, we refuse to do it. */
2878                         if (appData.debugMode)
2879                           fprintf(debugFP, "%d ", option);
2880                         TelnetRequest(TN_WONT, option);
2881                         break;
2882                     }
2883                     break;
2884                   case TN_DONT:
2885                     if (appData.debugMode)
2886                       fprintf(debugFP, "\n<DONT ");
2887                     switch (option = (unsigned char) buf[++i]) {
2888                       default:
2889                         if (appData.debugMode)
2890                           fprintf(debugFP, "%d ", option);
2891                         /* Whatever this is, we are already not doing
2892                            it, because we never agree to do anything
2893                            non-default, so according to the protocol
2894                            rules, we don't reply. */
2895                         break;
2896                     }
2897                     break;
2898                   case TN_IAC:
2899                     if (appData.debugMode)
2900                       fprintf(debugFP, "\n<IAC ");
2901                     /* Doubled IAC; pass it through */
2902                     i--;
2903                     break;
2904                   default:
2905                     if (appData.debugMode)
2906                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2907                     /* Drop all other telnet commands on the floor */
2908                     break;
2909                 }
2910                 if (oldi > next_out)
2911                   SendToPlayer(&buf[next_out], oldi - next_out);
2912                 if (++i > next_out)
2913                   next_out = i;
2914                 continue;
2915             }
2916
2917             /* OK, this at least will *usually* work */
2918             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2919                 loggedOn = TRUE;
2920             }
2921
2922             if (loggedOn && !intfSet) {
2923                 if (ics_type == ICS_ICC) {
2924                   snprintf(str, MSG_SIZ,
2925                           "/set-quietly interface %s\n/set-quietly style 12\n",
2926                           programVersion);
2927                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2928                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2929                 } else if (ics_type == ICS_CHESSNET) {
2930                   snprintf(str, MSG_SIZ, "/style 12\n");
2931                 } else {
2932                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2933                   strcat(str, programVersion);
2934                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2935                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2936                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2937 #ifdef WIN32
2938                   strcat(str, "$iset nohighlight 1\n");
2939 #endif
2940                   strcat(str, "$iset lock 1\n$style 12\n");
2941                 }
2942                 SendToICS(str);
2943                 NotifyFrontendLogin();
2944                 intfSet = TRUE;
2945             }
2946
2947             if (started == STARTED_COMMENT) {
2948                 /* Accumulate characters in comment */
2949                 parse[parse_pos++] = buf[i];
2950                 if (buf[i] == '\n') {
2951                     parse[parse_pos] = NULLCHAR;
2952                     if(chattingPartner>=0) {
2953                         char mess[MSG_SIZ];
2954                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2955                         OutputChatMessage(chattingPartner, mess);
2956                         chattingPartner = -1;
2957                         next_out = i+1; // [HGM] suppress printing in ICS window
2958                     } else
2959                     if(!suppressKibitz) // [HGM] kibitz
2960                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2961                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2962                         int nrDigit = 0, nrAlph = 0, j;
2963                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2964                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2965                         parse[parse_pos] = NULLCHAR;
2966                         // try to be smart: if it does not look like search info, it should go to
2967                         // ICS interaction window after all, not to engine-output window.
2968                         for(j=0; j<parse_pos; j++) { // count letters and digits
2969                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2970                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2971                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2972                         }
2973                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2974                             int depth=0; float score;
2975                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2976                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2977                                 pvInfoList[forwardMostMove-1].depth = depth;
2978                                 pvInfoList[forwardMostMove-1].score = 100*score;
2979                             }
2980                             OutputKibitz(suppressKibitz, parse);
2981                         } else {
2982                             char tmp[MSG_SIZ];
2983                             if(gameMode == IcsObserving) // restore original ICS messages
2984                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2985                             else
2986                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2987                             SendToPlayer(tmp, strlen(tmp));
2988                         }
2989                         next_out = i+1; // [HGM] suppress printing in ICS window
2990                     }
2991                     started = STARTED_NONE;
2992                 } else {
2993                     /* Don't match patterns against characters in comment */
2994                     i++;
2995                     continue;
2996                 }
2997             }
2998             if (started == STARTED_CHATTER) {
2999                 if (buf[i] != '\n') {
3000                     /* Don't match patterns against characters in chatter */
3001                     i++;
3002                     continue;
3003                 }
3004                 started = STARTED_NONE;
3005                 if(suppressKibitz) next_out = i+1;
3006             }
3007
3008             /* Kludge to deal with rcmd protocol */
3009             if (firstTime && looking_at(buf, &i, "\001*")) {
3010                 DisplayFatalError(&buf[1], 0, 1);
3011                 continue;
3012             } else {
3013                 firstTime = FALSE;
3014             }
3015
3016             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3017                 ics_type = ICS_ICC;
3018                 ics_prefix = "/";
3019                 if (appData.debugMode)
3020                   fprintf(debugFP, "ics_type %d\n", ics_type);
3021                 continue;
3022             }
3023             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3024                 ics_type = ICS_FICS;
3025                 ics_prefix = "$";
3026                 if (appData.debugMode)
3027                   fprintf(debugFP, "ics_type %d\n", ics_type);
3028                 continue;
3029             }
3030             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3031                 ics_type = ICS_CHESSNET;
3032                 ics_prefix = "/";
3033                 if (appData.debugMode)
3034                   fprintf(debugFP, "ics_type %d\n", ics_type);
3035                 continue;
3036             }
3037
3038             if (!loggedOn &&
3039                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3040                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3041                  looking_at(buf, &i, "will be \"*\""))) {
3042               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3043               continue;
3044             }
3045
3046             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3047               char buf[MSG_SIZ];
3048               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3049               DisplayIcsInteractionTitle(buf);
3050               have_set_title = TRUE;
3051             }
3052
3053             /* skip finger notes */
3054             if (started == STARTED_NONE &&
3055                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3056                  (buf[i] == '1' && buf[i+1] == '0')) &&
3057                 buf[i+2] == ':' && buf[i+3] == ' ') {
3058               started = STARTED_CHATTER;
3059               i += 3;
3060               continue;
3061             }
3062
3063             oldi = i;
3064             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3065             if(appData.seekGraph) {
3066                 if(soughtPending && MatchSoughtLine(buf+i)) {
3067                     i = strstr(buf+i, "rated") - buf;
3068                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069                     next_out = leftover_start = i;
3070                     started = STARTED_CHATTER;
3071                     suppressKibitz = TRUE;
3072                     continue;
3073                 }
3074                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3075                         && looking_at(buf, &i, "* ads displayed")) {
3076                     soughtPending = FALSE;
3077                     seekGraphUp = TRUE;
3078                     DrawSeekGraph();
3079                     continue;
3080                 }
3081                 if(appData.autoRefresh) {
3082                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3083                         int s = (ics_type == ICS_ICC); // ICC format differs
3084                         if(seekGraphUp)
3085                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3086                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3087                         looking_at(buf, &i, "*% "); // eat prompt
3088                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3089                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = i; // suppress
3091                         continue;
3092                     }
3093                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3094                         char *p = star_match[0];
3095                         while(*p) {
3096                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3097                             while(*p && *p++ != ' '); // next
3098                         }
3099                         looking_at(buf, &i, "*% "); // eat prompt
3100                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101                         next_out = i;
3102                         continue;
3103                     }
3104                 }
3105             }
3106
3107             /* skip formula vars */
3108             if (started == STARTED_NONE &&
3109                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3110               started = STARTED_CHATTER;
3111               i += 3;
3112               continue;
3113             }
3114
3115             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3116             if (appData.autoKibitz && started == STARTED_NONE &&
3117                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3118                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3119                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3120                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3121                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3122                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3123                         suppressKibitz = TRUE;
3124                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3125                         next_out = i;
3126                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3127                                 && (gameMode == IcsPlayingWhite)) ||
3128                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3129                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3130                             started = STARTED_CHATTER; // own kibitz we simply discard
3131                         else {
3132                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3133                             parse_pos = 0; parse[0] = NULLCHAR;
3134                             savingComment = TRUE;
3135                             suppressKibitz = gameMode != IcsObserving ? 2 :
3136                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3137                         }
3138                         continue;
3139                 } else
3140                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3141                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3142                          && atoi(star_match[0])) {
3143                     // suppress the acknowledgements of our own autoKibitz
3144                     char *p;
3145                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3146                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3147                     SendToPlayer(star_match[0], strlen(star_match[0]));
3148                     if(looking_at(buf, &i, "*% ")) // eat prompt
3149                         suppressKibitz = FALSE;
3150                     next_out = i;
3151                     continue;
3152                 }
3153             } // [HGM] kibitz: end of patch
3154
3155             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3156
3157             // [HGM] chat: intercept tells by users for which we have an open chat window
3158             channel = -1;
3159             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3160                                            looking_at(buf, &i, "* whispers:") ||
3161                                            looking_at(buf, &i, "* kibitzes:") ||
3162                                            looking_at(buf, &i, "* shouts:") ||
3163                                            looking_at(buf, &i, "* c-shouts:") ||
3164                                            looking_at(buf, &i, "--> * ") ||
3165                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3167                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3168                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3169                 int p;
3170                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3171                 chattingPartner = -1;
3172
3173                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3174                 for(p=0; p<MAX_CHAT; p++) {
3175                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3176                     talker[0] = '['; strcat(talker, "] ");
3177                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3178                     chattingPartner = p; break;
3179                     }
3180                 } else
3181                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3182                 for(p=0; p<MAX_CHAT; p++) {
3183                     if(!strcmp("kibitzes", chatPartner[p])) {
3184                         talker[0] = '['; strcat(talker, "] ");
3185                         chattingPartner = p; break;
3186                     }
3187                 } else
3188                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3189                 for(p=0; p<MAX_CHAT; p++) {
3190                     if(!strcmp("whispers", chatPartner[p])) {
3191                         talker[0] = '['; strcat(talker, "] ");
3192                         chattingPartner = p; break;
3193                     }
3194                 } else
3195                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3196                   if(buf[i-8] == '-' && buf[i-3] == 't')
3197                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3198                     if(!strcmp("c-shouts", chatPartner[p])) {
3199                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3200                         chattingPartner = p; break;
3201                     }
3202                   }
3203                   if(chattingPartner < 0)
3204                   for(p=0; p<MAX_CHAT; p++) {
3205                     if(!strcmp("shouts", chatPartner[p])) {
3206                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3207                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3208                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3209                         chattingPartner = p; break;
3210                     }
3211                   }
3212                 }
3213                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3214                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3215                     talker[0] = 0; Colorize(ColorTell, FALSE);
3216                     chattingPartner = p; break;
3217                 }
3218                 if(chattingPartner<0) i = oldi; else {
3219                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3220                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3221                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                     started = STARTED_COMMENT;
3223                     parse_pos = 0; parse[0] = NULLCHAR;
3224                     savingComment = 3 + chattingPartner; // counts as TRUE
3225                     suppressKibitz = TRUE;
3226                     continue;
3227                 }
3228             } // [HGM] chat: end of patch
3229
3230           backup = i;
3231             if (appData.zippyTalk || appData.zippyPlay) {
3232                 /* [DM] Backup address for color zippy lines */
3233 #if ZIPPY
3234                if (loggedOn == TRUE)
3235                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3236                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3237 #endif
3238             } // [DM] 'else { ' deleted
3239                 if (
3240                     /* Regular tells and says */
3241                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3242                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3243                     looking_at(buf, &i, "* says: ") ||
3244                     /* Don't color "message" or "messages" output */
3245                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3246                     looking_at(buf, &i, "*. * at *:*: ") ||
3247                     looking_at(buf, &i, "--* (*:*): ") ||
3248                     /* Message notifications (same color as tells) */
3249                     looking_at(buf, &i, "* has left a message ") ||
3250                     looking_at(buf, &i, "* just sent you a message:\n") ||
3251                     /* Whispers and kibitzes */
3252                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3253                     looking_at(buf, &i, "* kibitzes: ") ||
3254                     /* Channel tells */
3255                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3256
3257                   if (tkind == 1 && strchr(star_match[0], ':')) {
3258                       /* Avoid "tells you:" spoofs in channels */
3259                      tkind = 3;
3260                   }
3261                   if (star_match[0][0] == NULLCHAR ||
3262                       strchr(star_match[0], ' ') ||
3263                       (tkind == 3 && strchr(star_match[1], ' '))) {
3264                     /* Reject bogus matches */
3265                     i = oldi;
3266                   } else {
3267                     if (appData.colorize) {
3268                       if (oldi > next_out) {
3269                         SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = oldi;
3271                       }
3272                       switch (tkind) {
3273                       case 1:
3274                         Colorize(ColorTell, FALSE);
3275                         curColor = ColorTell;
3276                         break;
3277                       case 2:
3278                         Colorize(ColorKibitz, FALSE);
3279                         curColor = ColorKibitz;
3280                         break;
3281                       case 3:
3282                         p = strrchr(star_match[1], '(');
3283                         if (p == NULL) {
3284                           p = star_match[1];
3285                         } else {
3286                           p++;
3287                         }
3288                         if (atoi(p) == 1) {
3289                           Colorize(ColorChannel1, FALSE);
3290                           curColor = ColorChannel1;
3291                         } else {
3292                           Colorize(ColorChannel, FALSE);
3293                           curColor = ColorChannel;
3294                         }
3295                         break;
3296                       case 5:
3297                         curColor = ColorNormal;
3298                         break;
3299                       }
3300                     }
3301                     if (started == STARTED_NONE && appData.autoComment &&
3302                         (gameMode == IcsObserving ||
3303                          gameMode == IcsPlayingWhite ||
3304                          gameMode == IcsPlayingBlack)) {
3305                       parse_pos = i - oldi;
3306                       memcpy(parse, &buf[oldi], parse_pos);
3307                       parse[parse_pos] = NULLCHAR;
3308                       started = STARTED_COMMENT;
3309                       savingComment = TRUE;
3310                     } else {
3311                       started = STARTED_CHATTER;
3312                       savingComment = FALSE;
3313                     }
3314                     loggedOn = TRUE;
3315                     continue;
3316                   }
3317                 }
3318
3319                 if (looking_at(buf, &i, "* s-shouts: ") ||
3320                     looking_at(buf, &i, "* c-shouts: ")) {
3321                     if (appData.colorize) {
3322                         if (oldi > next_out) {
3323                             SendToPlayer(&buf[next_out], oldi - next_out);
3324                             next_out = oldi;
3325                         }
3326                         Colorize(ColorSShout, FALSE);
3327                         curColor = ColorSShout;
3328                     }
3329                     loggedOn = TRUE;
3330                     started = STARTED_CHATTER;
3331                     continue;
3332                 }
3333
3334                 if (looking_at(buf, &i, "--->")) {
3335                     loggedOn = TRUE;
3336                     continue;
3337                 }
3338
3339                 if (looking_at(buf, &i, "* shouts: ") ||
3340                     looking_at(buf, &i, "--> ")) {
3341                     if (appData.colorize) {
3342                         if (oldi > next_out) {
3343                             SendToPlayer(&buf[next_out], oldi - next_out);
3344                             next_out = oldi;
3345                         }
3346                         Colorize(ColorShout, FALSE);
3347                         curColor = ColorShout;
3348                     }
3349                     loggedOn = TRUE;
3350                     started = STARTED_CHATTER;
3351                     continue;
3352                 }
3353
3354                 if (looking_at( buf, &i, "Challenge:")) {
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorChallenge, FALSE);
3361                         curColor = ColorChallenge;
3362                     }
3363                     loggedOn = TRUE;
3364                     continue;
3365                 }
3366
3367                 if (looking_at(buf, &i, "* offers you") ||
3368                     looking_at(buf, &i, "* offers to be") ||
3369                     looking_at(buf, &i, "* would like to") ||
3370                     looking_at(buf, &i, "* requests to") ||
3371                     looking_at(buf, &i, "Your opponent offers") ||
3372                     looking_at(buf, &i, "Your opponent requests")) {
3373
3374                     if (appData.colorize) {
3375                         if (oldi > next_out) {
3376                             SendToPlayer(&buf[next_out], oldi - next_out);
3377                             next_out = oldi;
3378                         }
3379                         Colorize(ColorRequest, FALSE);
3380                         curColor = ColorRequest;
3381                     }
3382                     continue;
3383                 }
3384
3385                 if (looking_at(buf, &i, "* (*) seeking")) {
3386                     if (appData.colorize) {
3387                         if (oldi > next_out) {
3388                             SendToPlayer(&buf[next_out], oldi - next_out);
3389                             next_out = oldi;
3390                         }
3391                         Colorize(ColorSeek, FALSE);
3392                         curColor = ColorSeek;
3393                     }
3394                     continue;
3395             }
3396
3397           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3398
3399             if (looking_at(buf, &i, "\\   ")) {
3400                 if (prevColor != ColorNormal) {
3401                     if (oldi > next_out) {
3402                         SendToPlayer(&buf[next_out], oldi - next_out);
3403                         next_out = oldi;
3404                     }
3405                     Colorize(prevColor, TRUE);
3406                     curColor = prevColor;
3407                 }
3408                 if (savingComment) {
3409                     parse_pos = i - oldi;
3410                     memcpy(parse, &buf[oldi], parse_pos);
3411                     parse[parse_pos] = NULLCHAR;
3412                     started = STARTED_COMMENT;
3413                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3414                         chattingPartner = savingComment - 3; // kludge to remember the box
3415                 } else {
3416                     started = STARTED_CHATTER;
3417                 }
3418                 continue;
3419             }
3420
3421             if (looking_at(buf, &i, "Black Strength :") ||
3422                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3423                 looking_at(buf, &i, "<10>") ||
3424                 looking_at(buf, &i, "#@#")) {
3425                 /* Wrong board style */
3426                 loggedOn = TRUE;
3427                 SendToICS(ics_prefix);
3428                 SendToICS("set style 12\n");
3429                 SendToICS(ics_prefix);
3430                 SendToICS("refresh\n");
3431                 continue;
3432             }
3433
3434             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3435                 ICSInitScript();
3436                 have_sent_ICS_logon = 1;
3437                 continue;
3438             }
3439
3440             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3441                 (looking_at(buf, &i, "\n<12> ") ||
3442                  looking_at(buf, &i, "<12> "))) {
3443                 loggedOn = TRUE;
3444                 if (oldi > next_out) {
3445                     SendToPlayer(&buf[next_out], oldi - next_out);
3446                 }
3447                 next_out = i;
3448                 started = STARTED_BOARD;
3449                 parse_pos = 0;
3450                 continue;
3451             }
3452
3453             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3454                 looking_at(buf, &i, "<b1> ")) {
3455                 if (oldi > next_out) {
3456                     SendToPlayer(&buf[next_out], oldi - next_out);
3457                 }
3458                 next_out = i;
3459                 started = STARTED_HOLDINGS;
3460                 parse_pos = 0;
3461                 continue;
3462             }
3463
3464             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3465                 loggedOn = TRUE;
3466                 /* Header for a move list -- first line */
3467
3468                 switch (ics_getting_history) {
3469                   case H_FALSE:
3470                     switch (gameMode) {
3471                       case IcsIdle:
3472                       case BeginningOfGame:
3473                         /* User typed "moves" or "oldmoves" while we
3474                            were idle.  Pretend we asked for these
3475                            moves and soak them up so user can step
3476                            through them and/or save them.
3477                            */
3478                         Reset(FALSE, TRUE);
3479                         gameMode = IcsObserving;
3480                         ModeHighlight();
3481                         ics_gamenum = -1;
3482                         ics_getting_history = H_GOT_UNREQ_HEADER;
3483                         break;
3484                       case EditGame: /*?*/
3485                       case EditPosition: /*?*/
3486                         /* Should above feature work in these modes too? */
3487                         /* For now it doesn't */
3488                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3489                         break;
3490                       default:
3491                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3492                         break;
3493                     }
3494                     break;
3495                   case H_REQUESTED:
3496                     /* Is this the right one? */
3497                     if (gameInfo.white && gameInfo.black &&
3498                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3499                         strcmp(gameInfo.black, star_match[2]) == 0) {
3500                         /* All is well */
3501                         ics_getting_history = H_GOT_REQ_HEADER;
3502                     }
3503                     break;
3504                   case H_GOT_REQ_HEADER:
3505                   case H_GOT_UNREQ_HEADER:
3506                   case H_GOT_UNWANTED_HEADER:
3507                   case H_GETTING_MOVES:
3508                     /* Should not happen */
3509                     DisplayError(_("Error gathering move list: two headers"), 0);
3510                     ics_getting_history = H_FALSE;
3511                     break;
3512                 }
3513
3514                 /* Save player ratings into gameInfo if needed */
3515                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3516                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3517                     (gameInfo.whiteRating == -1 ||
3518                      gameInfo.blackRating == -1)) {
3519
3520                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3521                     gameInfo.blackRating = string_to_rating(star_match[3]);
3522                     if (appData.debugMode)
3523                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3524                               gameInfo.whiteRating, gameInfo.blackRating);
3525                 }
3526                 continue;
3527             }
3528
3529             if (looking_at(buf, &i,
3530               "* * match, initial time: * minute*, increment: * second")) {
3531                 /* Header for a move list -- second line */
3532                 /* Initial board will follow if this is a wild game */
3533                 if (gameInfo.event != NULL) free(gameInfo.event);
3534                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3535                 gameInfo.event = StrSave(str);
3536                 /* [HGM] we switched variant. Translate boards if needed. */
3537                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3538                 continue;
3539             }
3540
3541             if (looking_at(buf, &i, "Move  ")) {
3542                 /* Beginning of a move list */
3543                 switch (ics_getting_history) {
3544                   case H_FALSE:
3545                     /* Normally should not happen */
3546                     /* Maybe user hit reset while we were parsing */
3547                     break;
3548                   case H_REQUESTED:
3549                     /* Happens if we are ignoring a move list that is not
3550                      * the one we just requested.  Common if the user
3551                      * tries to observe two games without turning off
3552                      * getMoveList */
3553                     break;
3554                   case H_GETTING_MOVES:
3555                     /* Should not happen */
3556                     DisplayError(_("Error gathering move list: nested"), 0);
3557                     ics_getting_history = H_FALSE;
3558                     break;
3559                   case H_GOT_REQ_HEADER:
3560                     ics_getting_history = H_GETTING_MOVES;
3561                     started = STARTED_MOVES;
3562                     parse_pos = 0;
3563                     if (oldi > next_out) {
3564                         SendToPlayer(&buf[next_out], oldi - next_out);
3565                     }
3566                     break;
3567                   case H_GOT_UNREQ_HEADER:
3568                     ics_getting_history = H_GETTING_MOVES;
3569                     started = STARTED_MOVES_NOHIDE;
3570                     parse_pos = 0;
3571                     break;
3572                   case H_GOT_UNWANTED_HEADER:
3573                     ics_getting_history = H_FALSE;
3574                     break;
3575                 }
3576                 continue;
3577             }
3578
3579             if (looking_at(buf, &i, "% ") ||
3580                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3581                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3582                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3583                     soughtPending = FALSE;
3584                     seekGraphUp = TRUE;
3585                     DrawSeekGraph();
3586                 }
3587                 if(suppressKibitz) next_out = i;
3588                 savingComment = FALSE;
3589                 suppressKibitz = 0;
3590                 switch (started) {
3591                   case STARTED_MOVES:
3592                   case STARTED_MOVES_NOHIDE:
3593                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3594                     parse[parse_pos + i - oldi] = NULLCHAR;
3595                     ParseGameHistory(parse);
3596 #if ZIPPY
3597                     if (appData.zippyPlay && first.initDone) {
3598                         FeedMovesToProgram(&first, forwardMostMove);
3599                         if (gameMode == IcsPlayingWhite) {
3600                             if (WhiteOnMove(forwardMostMove)) {
3601                                 if (first.sendTime) {
3602                                   if (first.useColors) {
3603                                     SendToProgram("black\n", &first);
3604                                   }
3605                                   SendTimeRemaining(&first, TRUE);
3606                                 }
3607                                 if (first.useColors) {
3608                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3609                                 }
3610                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3611                                 first.maybeThinking = TRUE;
3612                             } else {
3613                                 if (first.usePlayother) {
3614                                   if (first.sendTime) {
3615                                     SendTimeRemaining(&first, TRUE);
3616                                   }
3617                                   SendToProgram("playother\n", &first);
3618                                   firstMove = FALSE;
3619                                 } else {
3620                                   firstMove = TRUE;
3621                                 }
3622                             }
3623                         } else if (gameMode == IcsPlayingBlack) {
3624                             if (!WhiteOnMove(forwardMostMove)) {
3625                                 if (first.sendTime) {
3626                                   if (first.useColors) {
3627                                     SendToProgram("white\n", &first);
3628                                   }
3629                                   SendTimeRemaining(&first, FALSE);
3630                                 }
3631                                 if (first.useColors) {
3632                                   SendToProgram("black\n", &first);
3633                                 }
3634                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3635                                 first.maybeThinking = TRUE;
3636                             } else {
3637                                 if (first.usePlayother) {
3638                                   if (first.sendTime) {
3639                                     SendTimeRemaining(&first, FALSE);
3640                                   }
3641                                   SendToProgram("playother\n", &first);
3642                                   firstMove = FALSE;
3643                                 } else {
3644                                   firstMove = TRUE;
3645                                 }
3646                             }
3647                         }
3648                     }
3649 #endif
3650                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3651                         /* Moves came from oldmoves or moves command
3652                            while we weren't doing anything else.
3653                            */
3654                         currentMove = forwardMostMove;
3655                         ClearHighlights();/*!!could figure this out*/
3656                         flipView = appData.flipView;
3657                         DrawPosition(TRUE, boards[currentMove]);
3658                         DisplayBothClocks();
3659                         snprintf(str, MSG_SIZ, "%s %s %s",
3660                                 gameInfo.white, _("vs."),  gameInfo.black);
3661                         DisplayTitle(str);
3662                         gameMode = IcsIdle;
3663                     } else {
3664                         /* Moves were history of an active game */
3665                         if (gameInfo.resultDetails != NULL) {
3666                             free(gameInfo.resultDetails);
3667                             gameInfo.resultDetails = NULL;
3668                         }
3669                     }
3670                     HistorySet(parseList, backwardMostMove,
3671                                forwardMostMove, currentMove-1);
3672                     DisplayMove(currentMove - 1);
3673                     if (started == STARTED_MOVES) next_out = i;
3674                     started = STARTED_NONE;
3675                     ics_getting_history = H_FALSE;
3676                     break;
3677
3678                   case STARTED_OBSERVE:
3679                     started = STARTED_NONE;
3680                     SendToICS(ics_prefix);
3681                     SendToICS("refresh\n");
3682                     break;
3683
3684                   default:
3685                     break;
3686                 }
3687                 if(bookHit) { // [HGM] book: simulate book reply
3688                     static char bookMove[MSG_SIZ]; // a bit generous?
3689
3690                     programStats.nodes = programStats.depth = programStats.time =
3691                     programStats.score = programStats.got_only_move = 0;
3692                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3693
3694                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3695                     strcat(bookMove, bookHit);
3696                     HandleMachineMove(bookMove, &first);
3697                 }
3698                 continue;
3699             }
3700
3701             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3702                  started == STARTED_HOLDINGS ||
3703                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3704                 /* Accumulate characters in move list or board */
3705                 parse[parse_pos++] = buf[i];
3706             }
3707
3708             /* Start of game messages.  Mostly we detect start of game
3709                when the first board image arrives.  On some versions
3710                of the ICS, though, we need to do a "refresh" after starting
3711                to observe in order to get the current board right away. */
3712             if (looking_at(buf, &i, "Adding game * to observation list")) {
3713                 started = STARTED_OBSERVE;
3714                 continue;
3715             }
3716
3717             /* Handle auto-observe */
3718             if (appData.autoObserve &&
3719                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3720                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3721                 char *player;
3722                 /* Choose the player that was highlighted, if any. */
3723                 if (star_match[0][0] == '\033' ||
3724                     star_match[1][0] != '\033') {
3725                     player = star_match[0];
3726                 } else {
3727                     player = star_match[2];
3728                 }
3729                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3730                         ics_prefix, StripHighlightAndTitle(player));
3731                 SendToICS(str);
3732
3733                 /* Save ratings from notify string */
3734                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3735                 player1Rating = string_to_rating(star_match[1]);
3736                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3737                 player2Rating = string_to_rating(star_match[3]);
3738
3739                 if (appData.debugMode)
3740                   fprintf(debugFP,
3741                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3742                           player1Name, player1Rating,
3743                           player2Name, player2Rating);
3744
3745                 continue;
3746             }
3747
3748             /* Deal with automatic examine mode after a game,
3749                and with IcsObserving -> IcsExamining transition */
3750             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3751                 looking_at(buf, &i, "has made you an examiner of game *")) {
3752
3753                 int gamenum = atoi(star_match[0]);
3754                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3755                     gamenum == ics_gamenum) {
3756                     /* We were already playing or observing this game;
3757                        no need to refetch history */
3758                     gameMode = IcsExamining;
3759                     if (pausing) {
3760                         pauseExamForwardMostMove = forwardMostMove;
3761                     } else if (currentMove < forwardMostMove) {
3762                         ForwardInner(forwardMostMove);
3763                     }
3764                 } else {
3765                     /* I don't think this case really can happen */
3766                     SendToICS(ics_prefix);
3767                     SendToICS("refresh\n");
3768                 }
3769                 continue;
3770             }
3771
3772             /* Error messages */
3773 //          if (ics_user_moved) {
3774             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3775                 if (looking_at(buf, &i, "Illegal move") ||
3776                     looking_at(buf, &i, "Not a legal move") ||
3777                     looking_at(buf, &i, "Your king is in check") ||
3778                     looking_at(buf, &i, "It isn't your turn") ||
3779                     looking_at(buf, &i, "It is not your move")) {
3780                     /* Illegal move */
3781                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3782                         currentMove = forwardMostMove-1;
3783                         DisplayMove(currentMove - 1); /* before DMError */
3784                         DrawPosition(FALSE, boards[currentMove]);
3785                         SwitchClocks(forwardMostMove-1); // [HGM] race
3786                         DisplayBothClocks();
3787                     }
3788                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3789                     ics_user_moved = 0;
3790                     continue;
3791                 }
3792             }
3793
3794             if (looking_at(buf, &i, "still have time") ||
3795                 looking_at(buf, &i, "not out of time") ||
3796                 looking_at(buf, &i, "either player is out of time") ||
3797                 looking_at(buf, &i, "has timeseal; checking")) {
3798                 /* We must have called his flag a little too soon */
3799                 whiteFlag = blackFlag = FALSE;
3800                 continue;
3801             }
3802
3803             if (looking_at(buf, &i, "added * seconds to") ||
3804                 looking_at(buf, &i, "seconds were added to")) {
3805                 /* Update the clocks */
3806                 SendToICS(ics_prefix);
3807                 SendToICS("refresh\n");
3808                 continue;
3809             }
3810
3811             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3812                 ics_clock_paused = TRUE;
3813                 StopClocks();
3814                 continue;
3815             }
3816
3817             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3818                 ics_clock_paused = FALSE;
3819                 StartClocks();
3820                 continue;
3821             }
3822
3823             /* Grab player ratings from the Creating: message.
3824                Note we have to check for the special case when
3825                the ICS inserts things like [white] or [black]. */
3826             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3827                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3828                 /* star_matches:
3829                    0    player 1 name (not necessarily white)
3830                    1    player 1 rating
3831                    2    empty, white, or black (IGNORED)
3832                    3    player 2 name (not necessarily black)
3833                    4    player 2 rating
3834
3835                    The names/ratings are sorted out when the game
3836                    actually starts (below).
3837                 */
3838                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3839                 player1Rating = string_to_rating(star_match[1]);
3840                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3841                 player2Rating = string_to_rating(star_match[4]);
3842
3843                 if (appData.debugMode)
3844                   fprintf(debugFP,
3845                           "Ratings from 'Creating:' %s %d, %s %d\n",
3846                           player1Name, player1Rating,
3847                           player2Name, player2Rating);
3848
3849                 continue;
3850             }
3851
3852             /* Improved generic start/end-of-game messages */
3853             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3854                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3855                 /* If tkind == 0: */
3856                 /* star_match[0] is the game number */
3857                 /*           [1] is the white player's name */
3858                 /*           [2] is the black player's name */
3859                 /* For end-of-game: */
3860                 /*           [3] is the reason for the game end */
3861                 /*           [4] is a PGN end game-token, preceded by " " */
3862                 /* For start-of-game: */
3863                 /*           [3] begins with "Creating" or "Continuing" */
3864                 /*           [4] is " *" or empty (don't care). */
3865                 int gamenum = atoi(star_match[0]);
3866                 char *whitename, *blackname, *why, *endtoken;
3867                 ChessMove endtype = EndOfFile;
3868
3869                 if (tkind == 0) {
3870                   whitename = star_match[1];
3871                   blackname = star_match[2];
3872                   why = star_match[3];
3873                   endtoken = star_match[4];
3874                 } else {
3875                   whitename = star_match[1];
3876                   blackname = star_match[3];
3877                   why = star_match[5];
3878                   endtoken = star_match[6];
3879                 }
3880
3881                 /* Game start messages */
3882                 if (strncmp(why, "Creating ", 9) == 0 ||
3883                     strncmp(why, "Continuing ", 11) == 0) {
3884                     gs_gamenum = gamenum;
3885                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3886                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3887                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3888 #if ZIPPY
3889                     if (appData.zippyPlay) {
3890                         ZippyGameStart(whitename, blackname);
3891                     }
3892 #endif /*ZIPPY*/
3893                     partnerBoardValid = FALSE; // [HGM] bughouse
3894                     continue;
3895                 }
3896
3897                 /* Game end messages */
3898                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3899                     ics_gamenum != gamenum) {
3900                     continue;
3901                 }
3902                 while (endtoken[0] == ' ') endtoken++;
3903                 switch (endtoken[0]) {
3904                   case '*':
3905                   default:
3906                     endtype = GameUnfinished;
3907                     break;
3908                   case '0':
3909                     endtype = BlackWins;
3910                     break;
3911                   case '1':
3912                     if (endtoken[1] == '/')
3913                       endtype = GameIsDrawn;
3914                     else
3915                       endtype = WhiteWins;
3916                     break;
3917                 }
3918                 GameEnds(endtype, why, GE_ICS);
3919 #if ZIPPY
3920                 if (appData.zippyPlay && first.initDone) {
3921                     ZippyGameEnd(endtype, why);
3922                     if (first.pr == NoProc) {
3923                       /* Start the next process early so that we'll
3924                          be ready for the next challenge */
3925                       StartChessProgram(&first);
3926                     }
3927                     /* Send "new" early, in case this command takes
3928                        a long time to finish, so that we'll be ready
3929                        for the next challenge. */
3930                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3931                     Reset(TRUE, TRUE);
3932                 }
3933 #endif /*ZIPPY*/
3934                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3935                 continue;
3936             }
3937
3938             if (looking_at(buf, &i, "Removing game * from observation") ||
3939                 looking_at(buf, &i, "no longer observing game *") ||
3940                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3941                 if (gameMode == IcsObserving &&
3942                     atoi(star_match[0]) == ics_gamenum)
3943                   {
3944                       /* icsEngineAnalyze */
3945                       if (appData.icsEngineAnalyze) {
3946                             ExitAnalyzeMode();
3947                             ModeHighlight();
3948                       }
3949                       StopClocks();
3950                       gameMode = IcsIdle;
3951                       ics_gamenum = -1;
3952                       ics_user_moved = FALSE;
3953                   }
3954                 continue;
3955             }
3956
3957             if (looking_at(buf, &i, "no longer examining game *")) {
3958                 if (gameMode == IcsExamining &&
3959                     atoi(star_match[0]) == ics_gamenum)
3960                   {
3961                       gameMode = IcsIdle;
3962                       ics_gamenum = -1;
3963                       ics_user_moved = FALSE;
3964                   }
3965                 continue;
3966             }
3967
3968             /* Advance leftover_start past any newlines we find,
3969                so only partial lines can get reparsed */
3970             if (looking_at(buf, &i, "\n")) {
3971                 prevColor = curColor;
3972                 if (curColor != ColorNormal) {
3973                     if (oldi > next_out) {
3974                         SendToPlayer(&buf[next_out], oldi - next_out);
3975                         next_out = oldi;
3976                     }
3977                     Colorize(ColorNormal, FALSE);
3978                     curColor = ColorNormal;
3979                 }
3980                 if (started == STARTED_BOARD) {
3981                     started = STARTED_NONE;
3982                     parse[parse_pos] = NULLCHAR;
3983                     ParseBoard12(parse);
3984                     ics_user_moved = 0;
3985
3986                     /* Send premove here */
3987                     if (appData.premove) {
3988                       char str[MSG_SIZ];
3989                       if (currentMove == 0 &&
3990                           gameMode == IcsPlayingWhite &&
3991                           appData.premoveWhite) {
3992                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3993                         if (appData.debugMode)
3994                           fprintf(debugFP, "Sending premove:\n");
3995                         SendToICS(str);
3996                       } else if (currentMove == 1 &&
3997                                  gameMode == IcsPlayingBlack &&
3998                                  appData.premoveBlack) {
3999                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4000                         if (appData.debugMode)
4001                           fprintf(debugFP, "Sending premove:\n");
4002                         SendToICS(str);
4003                       } else if (gotPremove) {
4004                         gotPremove = 0;
4005                         ClearPremoveHighlights();
4006                         if (appData.debugMode)
4007                           fprintf(debugFP, "Sending premove:\n");
4008                           UserMoveEvent(premoveFromX, premoveFromY,
4009                                         premoveToX, premoveToY,
4010                                         premovePromoChar);
4011                       }
4012                     }
4013
4014                     /* Usually suppress following prompt */
4015                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4016                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4017                         if (looking_at(buf, &i, "*% ")) {
4018                             savingComment = FALSE;
4019                             suppressKibitz = 0;
4020                         }
4021                     }
4022                     next_out = i;
4023                 } else if (started == STARTED_HOLDINGS) {
4024                     int gamenum;
4025                     char new_piece[MSG_SIZ];
4026                     started = STARTED_NONE;
4027                     parse[parse_pos] = NULLCHAR;
4028                     if (appData.debugMode)
4029                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4030                                                         parse, currentMove);
4031                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4032                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4033                         if (gameInfo.variant == VariantNormal) {
4034                           /* [HGM] We seem to switch variant during a game!
4035                            * Presumably no holdings were displayed, so we have
4036                            * to move the position two files to the right to
4037                            * create room for them!
4038                            */
4039                           VariantClass newVariant;
4040                           switch(gameInfo.boardWidth) { // base guess on board width
4041                                 case 9:  newVariant = VariantShogi; break;
4042                                 case 10: newVariant = VariantGreat; break;
4043                                 default: newVariant = VariantCrazyhouse; break;
4044                           }
4045                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4046                           /* Get a move list just to see the header, which
4047                              will tell us whether this is really bug or zh */
4048                           if (ics_getting_history == H_FALSE) {
4049                             ics_getting_history = H_REQUESTED;
4050                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4051                             SendToICS(str);
4052                           }
4053                         }
4054                         new_piece[0] = NULLCHAR;
4055                         sscanf(parse, "game %d white [%s black [%s <- %s",
4056                                &gamenum, white_holding, black_holding,
4057                                new_piece);
4058                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4059                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4060                         /* [HGM] copy holdings to board holdings area */
4061                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4062                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4063                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4064 #if ZIPPY
4065                         if (appData.zippyPlay && first.initDone) {
4066                             ZippyHoldings(white_holding, black_holding,
4067                                           new_piece);
4068                         }
4069 #endif /*ZIPPY*/
4070                         if (tinyLayout || smallLayout) {
4071                             char wh[16], bh[16];
4072                             PackHolding(wh, white_holding);
4073                             PackHolding(bh, black_holding);
4074                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4075                                     gameInfo.white, gameInfo.black);
4076                         } else {
4077                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4078                                     gameInfo.white, white_holding, _("vs."),
4079                                     gameInfo.black, black_holding);
4080                         }
4081                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4082                         DrawPosition(FALSE, boards[currentMove]);
4083                         DisplayTitle(str);
4084                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4085                         sscanf(parse, "game %d white [%s black [%s <- %s",
4086                                &gamenum, white_holding, black_holding,
4087                                new_piece);
4088                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4089                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4090                         /* [HGM] copy holdings to partner-board holdings area */
4091                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4092                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4093                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4094                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4095                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4096                       }
4097                     }
4098                     /* Suppress following prompt */
4099                     if (looking_at(buf, &i, "*% ")) {
4100                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4101                         savingComment = FALSE;
4102                         suppressKibitz = 0;
4103                     }
4104                     next_out = i;
4105                 }
4106                 continue;
4107             }
4108
4109             i++;                /* skip unparsed character and loop back */
4110         }
4111
4112         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4113 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4114 //          SendToPlayer(&buf[next_out], i - next_out);
4115             started != STARTED_HOLDINGS && leftover_start > next_out) {
4116             SendToPlayer(&buf[next_out], leftover_start - next_out);
4117             next_out = i;
4118         }
4119
4120         leftover_len = buf_len - leftover_start;
4121         /* if buffer ends with something we couldn't parse,
4122            reparse it after appending the next read */
4123
4124     } else if (count == 0) {
4125         RemoveInputSource(isr);
4126         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4127     } else {
4128         DisplayFatalError(_("Error reading from ICS"), error, 1);
4129     }
4130 }
4131
4132
4133 /* Board style 12 looks like this:
4134
4135    <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
4136
4137  * The "<12> " is stripped before it gets to this routine.  The two
4138  * trailing 0's (flip state and clock ticking) are later addition, and
4139  * some chess servers may not have them, or may have only the first.
4140  * Additional trailing fields may be added in the future.
4141  */
4142
4143 #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"
4144
4145 #define RELATION_OBSERVING_PLAYED    0
4146 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4147 #define RELATION_PLAYING_MYMOVE      1
4148 #define RELATION_PLAYING_NOTMYMOVE  -1
4149 #define RELATION_EXAMINING           2
4150 #define RELATION_ISOLATED_BOARD     -3
4151 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4152
4153 void
4154 ParseBoard12 (char *string)
4155 {
4156     GameMode newGameMode;
4157     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4158     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4159     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4160     char to_play, board_chars[200];
4161     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4162     char black[32], white[32];
4163     Board board;
4164     int prevMove = currentMove;
4165     int ticking = 2;
4166     ChessMove moveType;
4167     int fromX, fromY, toX, toY;
4168     char promoChar;
4169     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4170     char *bookHit = NULL; // [HGM] book
4171     Boolean weird = FALSE, reqFlag = FALSE;
4172
4173     fromX = fromY = toX = toY = -1;
4174
4175     newGame = FALSE;
4176
4177     if (appData.debugMode)
4178       fprintf(debugFP, _("Parsing board: %s\n"), string);
4179
4180     move_str[0] = NULLCHAR;
4181     elapsed_time[0] = NULLCHAR;
4182     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4183         int  i = 0, j;
4184         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4185             if(string[i] == ' ') { ranks++; files = 0; }
4186             else files++;
4187             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4188             i++;
4189         }
4190         for(j = 0; j <i; j++) board_chars[j] = string[j];
4191         board_chars[i] = '\0';
4192         string += i + 1;
4193     }
4194     n = sscanf(string, PATTERN, &to_play, &double_push,
4195                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4196                &gamenum, white, black, &relation, &basetime, &increment,
4197                &white_stren, &black_stren, &white_time, &black_time,
4198                &moveNum, str, elapsed_time, move_str, &ics_flip,
4199                &ticking);
4200
4201     if (n < 21) {
4202         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4203         DisplayError(str, 0);
4204         return;
4205     }
4206
4207     /* Convert the move number to internal form */
4208     moveNum = (moveNum - 1) * 2;
4209     if (to_play == 'B') moveNum++;
4210     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4211       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4212                         0, 1);
4213       return;
4214     }
4215
4216     switch (relation) {
4217       case RELATION_OBSERVING_PLAYED:
4218       case RELATION_OBSERVING_STATIC:
4219         if (gamenum == -1) {
4220             /* Old ICC buglet */
4221             relation = RELATION_OBSERVING_STATIC;
4222         }
4223         newGameMode = IcsObserving;
4224         break;
4225       case RELATION_PLAYING_MYMOVE:
4226       case RELATION_PLAYING_NOTMYMOVE:
4227         newGameMode =
4228           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4229             IcsPlayingWhite : IcsPlayingBlack;
4230         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4231         break;
4232       case RELATION_EXAMINING:
4233         newGameMode = IcsExamining;
4234         break;
4235       case RELATION_ISOLATED_BOARD:
4236       default:
4237         /* Just display this board.  If user was doing something else,
4238            we will forget about it until the next board comes. */
4239         newGameMode = IcsIdle;
4240         break;
4241       case RELATION_STARTING_POSITION:
4242         newGameMode = gameMode;
4243         break;
4244     }
4245
4246     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4247         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4248          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4249       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4250       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4251       static int lastBgGame = -1;
4252       char *toSqr;
4253       for (k = 0; k < ranks; k++) {
4254         for (j = 0; j < files; j++)
4255           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4256         if(gameInfo.holdingsWidth > 1) {
4257              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4258              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4259         }
4260       }
4261       CopyBoard(partnerBoard, board);
4262       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4263         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4264         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4265       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4266       if(toSqr = strchr(str, '-')) {
4267         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4268         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4269       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4270       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4271       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4272       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4273       if(twoBoards) {
4274           DisplayWhiteClock(white_time*fac, to_play == 'W');
4275           DisplayBlackClock(black_time*fac, to_play != 'W');
4276           activePartner = to_play;
4277           if(gamenum != lastBgGame) {
4278               char buf[MSG_SIZ];
4279               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4280               DisplayTitle(buf);
4281           }
4282           lastBgGame = gamenum;
4283           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4284                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4285       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4286                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4287       DisplayMessage(partnerStatus, "");
4288         partnerBoardValid = TRUE;
4289       return;
4290     }
4291
4292     if(appData.dualBoard && appData.bgObserve) {
4293         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4294             SendToICS(ics_prefix), SendToICS("pobserve\n");
4295         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4296             char buf[MSG_SIZ];
4297             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4298             SendToICS(buf);
4299         }
4300     }
4301
4302     /* Modify behavior for initial board display on move listing
4303        of wild games.
4304        */
4305     switch (ics_getting_history) {
4306       case H_FALSE:
4307       case H_REQUESTED:
4308         break;
4309       case H_GOT_REQ_HEADER:
4310       case H_GOT_UNREQ_HEADER:
4311         /* This is the initial position of the current game */
4312         gamenum = ics_gamenum;
4313         moveNum = 0;            /* old ICS bug workaround */
4314         if (to_play == 'B') {
4315           startedFromSetupPosition = TRUE;
4316           blackPlaysFirst = TRUE;
4317           moveNum = 1;
4318           if (forwardMostMove == 0) forwardMostMove = 1;
4319           if (backwardMostMove == 0) backwardMostMove = 1;
4320           if (currentMove == 0) currentMove = 1;
4321         }
4322         newGameMode = gameMode;
4323         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4324         break;
4325       case H_GOT_UNWANTED_HEADER:
4326         /* This is an initial board that we don't want */
4327         return;
4328       case H_GETTING_MOVES:
4329         /* Should not happen */
4330         DisplayError(_("Error gathering move list: extra board"), 0);
4331         ics_getting_history = H_FALSE;
4332         return;
4333     }
4334
4335    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4336                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4337                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4338      /* [HGM] We seem to have switched variant unexpectedly
4339       * Try to guess new variant from board size
4340       */
4341           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4342           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4343           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4344           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4345           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4346           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4347           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4348           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4349           /* Get a move list just to see the header, which
4350              will tell us whether this is really bug or zh */
4351           if (ics_getting_history == H_FALSE) {
4352             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4353             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4354             SendToICS(str);
4355           }
4356     }
4357
4358     /* Take action if this is the first board of a new game, or of a
4359        different game than is currently being displayed.  */
4360     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4361         relation == RELATION_ISOLATED_BOARD) {
4362
4363         /* Forget the old game and get the history (if any) of the new one */
4364         if (gameMode != BeginningOfGame) {
4365           Reset(TRUE, TRUE);
4366         }
4367         newGame = TRUE;
4368         if (appData.autoRaiseBoard) BoardToTop();
4369         prevMove = -3;
4370         if (gamenum == -1) {
4371             newGameMode = IcsIdle;
4372         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4373                    appData.getMoveList && !reqFlag) {
4374             /* Need to get game history */
4375             ics_getting_history = H_REQUESTED;
4376             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4377             SendToICS(str);
4378         }
4379
4380         /* Initially flip the board to have black on the bottom if playing
4381            black or if the ICS flip flag is set, but let the user change
4382            it with the Flip View button. */
4383         flipView = appData.autoFlipView ?
4384           (newGameMode == IcsPlayingBlack) || ics_flip :
4385           appData.flipView;
4386
4387         /* Done with values from previous mode; copy in new ones */
4388         gameMode = newGameMode;
4389         ModeHighlight();
4390         ics_gamenum = gamenum;
4391         if (gamenum == gs_gamenum) {
4392             int klen = strlen(gs_kind);
4393             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4394             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4395             gameInfo.event = StrSave(str);
4396         } else {
4397             gameInfo.event = StrSave("ICS game");
4398         }
4399         gameInfo.site = StrSave(appData.icsHost);
4400         gameInfo.date = PGNDate();
4401         gameInfo.round = StrSave("-");
4402         gameInfo.white = StrSave(white);
4403         gameInfo.black = StrSave(black);
4404         timeControl = basetime * 60 * 1000;
4405         timeControl_2 = 0;
4406         timeIncrement = increment * 1000;
4407         movesPerSession = 0;
4408         gameInfo.timeControl = TimeControlTagValue();
4409         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4410   if (appData.debugMode) {
4411     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4412     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4413     setbuf(debugFP, NULL);
4414   }
4415
4416         gameInfo.outOfBook = NULL;
4417
4418         /* Do we have the ratings? */
4419         if (strcmp(player1Name, white) == 0 &&
4420             strcmp(player2Name, black) == 0) {
4421             if (appData.debugMode)
4422               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4423                       player1Rating, player2Rating);
4424             gameInfo.whiteRating = player1Rating;
4425             gameInfo.blackRating = player2Rating;
4426         } else if (strcmp(player2Name, white) == 0 &&
4427                    strcmp(player1Name, black) == 0) {
4428             if (appData.debugMode)
4429               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4430                       player2Rating, player1Rating);
4431             gameInfo.whiteRating = player2Rating;
4432             gameInfo.blackRating = player1Rating;
4433         }
4434         player1Name[0] = player2Name[0] = NULLCHAR;
4435
4436         /* Silence shouts if requested */
4437         if (appData.quietPlay &&
4438             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4439             SendToICS(ics_prefix);
4440             SendToICS("set shout 0\n");
4441         }
4442     }
4443
4444     /* Deal with midgame name changes */
4445     if (!newGame) {
4446         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4447             if (gameInfo.white) free(gameInfo.white);
4448             gameInfo.white = StrSave(white);
4449         }
4450         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4451             if (gameInfo.black) free(gameInfo.black);
4452             gameInfo.black = StrSave(black);
4453         }
4454     }
4455
4456     /* Throw away game result if anything actually changes in examine mode */
4457     if (gameMode == IcsExamining && !newGame) {
4458         gameInfo.result = GameUnfinished;
4459         if (gameInfo.resultDetails != NULL) {
4460             free(gameInfo.resultDetails);
4461             gameInfo.resultDetails = NULL;
4462         }
4463     }
4464
4465     /* In pausing && IcsExamining mode, we ignore boards coming
4466        in if they are in a different variation than we are. */
4467     if (pauseExamInvalid) return;
4468     if (pausing && gameMode == IcsExamining) {
4469         if (moveNum <= pauseExamForwardMostMove) {
4470             pauseExamInvalid = TRUE;
4471             forwardMostMove = pauseExamForwardMostMove;
4472             return;
4473         }
4474     }
4475
4476   if (appData.debugMode) {
4477     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4478   }
4479     /* Parse the board */
4480     for (k = 0; k < ranks; k++) {
4481       for (j = 0; j < files; j++)
4482         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4483       if(gameInfo.holdingsWidth > 1) {
4484            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4485            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4486       }
4487     }
4488     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4489       board[5][BOARD_RGHT+1] = WhiteAngel;
4490       board[6][BOARD_RGHT+1] = WhiteMarshall;
4491       board[1][0] = BlackMarshall;
4492       board[2][0] = BlackAngel;
4493       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4494     }
4495     CopyBoard(boards[moveNum], board);
4496     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4497     if (moveNum == 0) {
4498         startedFromSetupPosition =
4499           !CompareBoards(board, initialPosition);
4500         if(startedFromSetupPosition)
4501             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4502     }
4503
4504     /* [HGM] Set castling rights. Take the outermost Rooks,
4505        to make it also work for FRC opening positions. Note that board12
4506        is really defective for later FRC positions, as it has no way to
4507        indicate which Rook can castle if they are on the same side of King.
4508        For the initial position we grant rights to the outermost Rooks,
4509        and remember thos rights, and we then copy them on positions
4510        later in an FRC game. This means WB might not recognize castlings with
4511        Rooks that have moved back to their original position as illegal,
4512        but in ICS mode that is not its job anyway.
4513     */
4514     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4515     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4516
4517         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4518             if(board[0][i] == WhiteRook) j = i;
4519         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4520         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4521             if(board[0][i] == WhiteRook) j = i;
4522         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4523         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4524             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4525         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4526         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4527             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4528         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4529
4530         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4531         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4532         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4533             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4534         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4535             if(board[BOARD_HEIGHT-1][k] == bKing)
4536                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4537         if(gameInfo.variant == VariantTwoKings) {
4538             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4539             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4540             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4541         }
4542     } else { int r;
4543         r = boards[moveNum][CASTLING][0] = initialRights[0];
4544         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4545         r = boards[moveNum][CASTLING][1] = initialRights[1];
4546         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4547         r = boards[moveNum][CASTLING][3] = initialRights[3];
4548         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4549         r = boards[moveNum][CASTLING][4] = initialRights[4];
4550         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4551         /* wildcastle kludge: always assume King has rights */
4552         r = boards[moveNum][CASTLING][2] = initialRights[2];
4553         r = boards[moveNum][CASTLING][5] = initialRights[5];
4554     }
4555     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4556     boards[moveNum][EP_STATUS] = EP_NONE;
4557     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4558     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4559     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4560
4561
4562     if (ics_getting_history == H_GOT_REQ_HEADER ||
4563         ics_getting_history == H_GOT_UNREQ_HEADER) {
4564         /* This was an initial position from a move list, not
4565            the current position */
4566         return;
4567     }
4568
4569     /* Update currentMove and known move number limits */
4570     newMove = newGame || moveNum > forwardMostMove;
4571
4572     if (newGame) {
4573         forwardMostMove = backwardMostMove = currentMove = moveNum;
4574         if (gameMode == IcsExamining && moveNum == 0) {
4575           /* Workaround for ICS limitation: we are not told the wild
4576              type when starting to examine a game.  But if we ask for
4577              the move list, the move list header will tell us */
4578             ics_getting_history = H_REQUESTED;
4579             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4580             SendToICS(str);
4581         }
4582     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4583                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4584 #if ZIPPY
4585         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4586         /* [HGM] applied this also to an engine that is silently watching        */
4587         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4588             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4589             gameInfo.variant == currentlyInitializedVariant) {
4590           takeback = forwardMostMove - moveNum;
4591           for (i = 0; i < takeback; i++) {
4592             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4593             SendToProgram("undo\n", &first);
4594           }
4595         }
4596 #endif
4597
4598         forwardMostMove = moveNum;
4599         if (!pausing || currentMove > forwardMostMove)
4600           currentMove = forwardMostMove;
4601     } else {
4602         /* New part of history that is not contiguous with old part */
4603         if (pausing && gameMode == IcsExamining) {
4604             pauseExamInvalid = TRUE;
4605             forwardMostMove = pauseExamForwardMostMove;
4606             return;
4607         }
4608         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4609 #if ZIPPY
4610             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4611                 // [HGM] when we will receive the move list we now request, it will be
4612                 // fed to the engine from the first move on. So if the engine is not
4613                 // in the initial position now, bring it there.
4614                 InitChessProgram(&first, 0);
4615             }
4616 #endif
4617             ics_getting_history = H_REQUESTED;
4618             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4619             SendToICS(str);
4620         }
4621         forwardMostMove = backwardMostMove = currentMove = moveNum;
4622     }
4623
4624     /* Update the clocks */
4625     if (strchr(elapsed_time, '.')) {
4626       /* Time is in ms */
4627       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4628       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4629     } else {
4630       /* Time is in seconds */
4631       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4632       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4633     }
4634
4635
4636 #if ZIPPY
4637     if (appData.zippyPlay && newGame &&
4638         gameMode != IcsObserving && gameMode != IcsIdle &&
4639         gameMode != IcsExamining)
4640       ZippyFirstBoard(moveNum, basetime, increment);
4641 #endif
4642
4643     /* Put the move on the move list, first converting
4644        to canonical algebraic form. */
4645     if (moveNum > 0) {
4646   if (appData.debugMode) {
4647     if (appData.debugMode) { int f = forwardMostMove;
4648         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4649                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4650                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4651     }
4652     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4653     fprintf(debugFP, "moveNum = %d\n", moveNum);
4654     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4655     setbuf(debugFP, NULL);
4656   }
4657         if (moveNum <= backwardMostMove) {
4658             /* We don't know what the board looked like before
4659                this move.  Punt. */
4660           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4661             strcat(parseList[moveNum - 1], " ");
4662             strcat(parseList[moveNum - 1], elapsed_time);
4663             moveList[moveNum - 1][0] = NULLCHAR;
4664         } else if (strcmp(move_str, "none") == 0) {
4665             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4666             /* Again, we don't know what the board looked like;
4667                this is really the start of the game. */
4668             parseList[moveNum - 1][0] = NULLCHAR;
4669             moveList[moveNum - 1][0] = NULLCHAR;
4670             backwardMostMove = moveNum;
4671             startedFromSetupPosition = TRUE;
4672             fromX = fromY = toX = toY = -1;
4673         } else {
4674           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4675           //                 So we parse the long-algebraic move string in stead of the SAN move
4676           int valid; char buf[MSG_SIZ], *prom;
4677
4678           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4679                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4680           // str looks something like "Q/a1-a2"; kill the slash
4681           if(str[1] == '/')
4682             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4683           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4684           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4685                 strcat(buf, prom); // long move lacks promo specification!
4686           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4687                 if(appData.debugMode)
4688                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4689                 safeStrCpy(move_str, buf, MSG_SIZ);
4690           }
4691           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4692                                 &fromX, &fromY, &toX, &toY, &promoChar)
4693                || ParseOneMove(buf, moveNum - 1, &moveType,
4694                                 &fromX, &fromY, &toX, &toY, &promoChar);
4695           // end of long SAN patch
4696           if (valid) {
4697             (void) CoordsToAlgebraic(boards[moveNum - 1],
4698                                      PosFlags(moveNum - 1),
4699                                      fromY, fromX, toY, toX, promoChar,
4700                                      parseList[moveNum-1]);
4701             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4702               case MT_NONE:
4703               case MT_STALEMATE:
4704               default:
4705                 break;
4706               case MT_CHECK:
4707                 if(gameInfo.variant != VariantShogi)
4708                     strcat(parseList[moveNum - 1], "+");
4709                 break;
4710               case MT_CHECKMATE:
4711               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4712                 strcat(parseList[moveNum - 1], "#");
4713                 break;
4714             }
4715             strcat(parseList[moveNum - 1], " ");
4716             strcat(parseList[moveNum - 1], elapsed_time);
4717             /* currentMoveString is set as a side-effect of ParseOneMove */
4718             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4719             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4720             strcat(moveList[moveNum - 1], "\n");
4721
4722             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4723                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4724               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4725                 ChessSquare old, new = boards[moveNum][k][j];
4726                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4727                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4728                   if(old == new) continue;
4729                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4730                   else if(new == WhiteWazir || new == BlackWazir) {
4731                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4732                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4733                       else boards[moveNum][k][j] = old; // preserve type of Gold
4734                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4735                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4736               }
4737           } else {
4738             /* Move from ICS was illegal!?  Punt. */
4739             if (appData.debugMode) {
4740               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4741               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4742             }
4743             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4744             strcat(parseList[moveNum - 1], " ");
4745             strcat(parseList[moveNum - 1], elapsed_time);
4746             moveList[moveNum - 1][0] = NULLCHAR;
4747             fromX = fromY = toX = toY = -1;
4748           }
4749         }
4750   if (appData.debugMode) {
4751     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4752     setbuf(debugFP, NULL);
4753   }
4754
4755 #if ZIPPY
4756         /* Send move to chess program (BEFORE animating it). */
4757         if (appData.zippyPlay && !newGame && newMove &&
4758            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4759
4760             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4761                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4762                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4763                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4764                             move_str);
4765                     DisplayError(str, 0);
4766                 } else {
4767                     if (first.sendTime) {
4768                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4769                     }
4770                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4771                     if (firstMove && !bookHit) {
4772                         firstMove = FALSE;
4773                         if (first.useColors) {
4774                           SendToProgram(gameMode == IcsPlayingWhite ?
4775                                         "white\ngo\n" :
4776                                         "black\ngo\n", &first);
4777                         } else {
4778                           SendToProgram("go\n", &first);
4779                         }
4780                         first.maybeThinking = TRUE;
4781                     }
4782                 }
4783             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4784               if (moveList[moveNum - 1][0] == NULLCHAR) {
4785                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4786                 DisplayError(str, 0);
4787               } else {
4788                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4789                 SendMoveToProgram(moveNum - 1, &first);
4790               }
4791             }
4792         }
4793 #endif
4794     }
4795
4796     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4797         /* If move comes from a remote source, animate it.  If it
4798            isn't remote, it will have already been animated. */
4799         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4800             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4801         }
4802         if (!pausing && appData.highlightLastMove) {
4803             SetHighlights(fromX, fromY, toX, toY);
4804         }
4805     }
4806
4807     /* Start the clocks */
4808     whiteFlag = blackFlag = FALSE;
4809     appData.clockMode = !(basetime == 0 && increment == 0);
4810     if (ticking == 0) {
4811       ics_clock_paused = TRUE;
4812       StopClocks();
4813     } else if (ticking == 1) {
4814       ics_clock_paused = FALSE;
4815     }
4816     if (gameMode == IcsIdle ||
4817         relation == RELATION_OBSERVING_STATIC ||
4818         relation == RELATION_EXAMINING ||
4819         ics_clock_paused)
4820       DisplayBothClocks();
4821     else
4822       StartClocks();
4823
4824     /* Display opponents and material strengths */
4825     if (gameInfo.variant != VariantBughouse &&
4826         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4827         if (tinyLayout || smallLayout) {
4828             if(gameInfo.variant == VariantNormal)
4829               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4830                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4831                     basetime, increment);
4832             else
4833               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4834                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4835                     basetime, increment, (int) gameInfo.variant);
4836         } else {
4837             if(gameInfo.variant == VariantNormal)
4838               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4839                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4840                     basetime, increment);
4841             else
4842               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4843                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4844                     basetime, increment, VariantName(gameInfo.variant));
4845         }
4846         DisplayTitle(str);
4847   if (appData.debugMode) {
4848     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4849   }
4850     }
4851
4852
4853     /* Display the board */
4854     if (!pausing && !appData.noGUI) {
4855
4856       if (appData.premove)
4857           if (!gotPremove ||
4858              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4859              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4860               ClearPremoveHighlights();
4861
4862       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4863         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4864       DrawPosition(j, boards[currentMove]);
4865
4866       DisplayMove(moveNum - 1);
4867       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4868             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4869               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4870         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4871       }
4872     }
4873
4874     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4875 #if ZIPPY
4876     if(bookHit) { // [HGM] book: simulate book reply
4877         static char bookMove[MSG_SIZ]; // a bit generous?
4878
4879         programStats.nodes = programStats.depth = programStats.time =
4880         programStats.score = programStats.got_only_move = 0;
4881         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4882
4883         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4884         strcat(bookMove, bookHit);
4885         HandleMachineMove(bookMove, &first);
4886     }
4887 #endif
4888 }
4889
4890 void
4891 GetMoveListEvent ()
4892 {
4893     char buf[MSG_SIZ];
4894     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4895         ics_getting_history = H_REQUESTED;
4896         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4897         SendToICS(buf);
4898     }
4899 }
4900
4901 void
4902 SendToBoth (char *msg)
4903 {   // to make it easy to keep two engines in step in dual analysis
4904     SendToProgram(msg, &first);
4905     if(second.analyzing) SendToProgram(msg, &second);
4906 }
4907
4908 void
4909 AnalysisPeriodicEvent (int force)
4910 {
4911     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4912          && !force) || !appData.periodicUpdates)
4913       return;
4914
4915     /* Send . command to Crafty to collect stats */
4916     SendToBoth(".\n");
4917
4918     /* Don't send another until we get a response (this makes
4919        us stop sending to old Crafty's which don't understand
4920        the "." command (sending illegal cmds resets node count & time,
4921        which looks bad)) */
4922     programStats.ok_to_send = 0;
4923 }
4924
4925 void
4926 ics_update_width (int new_width)
4927 {
4928         ics_printf("set width %d\n", new_width);
4929 }
4930
4931 void
4932 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4933 {
4934     char buf[MSG_SIZ];
4935
4936     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4937         // null move in variant where engine does not understand it (for analysis purposes)
4938         SendBoard(cps, moveNum + 1); // send position after move in stead.
4939         return;
4940     }
4941     if (cps->useUsermove) {
4942       SendToProgram("usermove ", cps);
4943     }
4944     if (cps->useSAN) {
4945       char *space;
4946       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4947         int len = space - parseList[moveNum];
4948         memcpy(buf, parseList[moveNum], len);
4949         buf[len++] = '\n';
4950         buf[len] = NULLCHAR;
4951       } else {
4952         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4953       }
4954       SendToProgram(buf, cps);
4955     } else {
4956       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4957         AlphaRank(moveList[moveNum], 4);
4958         SendToProgram(moveList[moveNum], cps);
4959         AlphaRank(moveList[moveNum], 4); // and back
4960       } else
4961       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4962        * the engine. It would be nice to have a better way to identify castle
4963        * moves here. */
4964       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4965                                                                          && cps->useOOCastle) {
4966         int fromX = moveList[moveNum][0] - AAA;
4967         int fromY = moveList[moveNum][1] - ONE;
4968         int toX = moveList[moveNum][2] - AAA;
4969         int toY = moveList[moveNum][3] - ONE;
4970         if((boards[moveNum][fromY][fromX] == WhiteKing
4971             && boards[moveNum][toY][toX] == WhiteRook)
4972            || (boards[moveNum][fromY][fromX] == BlackKing
4973                && boards[moveNum][toY][toX] == BlackRook)) {
4974           if(toX > fromX) SendToProgram("O-O\n", cps);
4975           else SendToProgram("O-O-O\n", cps);
4976         }
4977         else SendToProgram(moveList[moveNum], cps);
4978       } else
4979       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4980         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4981           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4982           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4983                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4984         } else
4985           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4986                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4987         SendToProgram(buf, cps);
4988       }
4989       else SendToProgram(moveList[moveNum], cps);
4990       /* End of additions by Tord */
4991     }
4992
4993     /* [HGM] setting up the opening has brought engine in force mode! */
4994     /*       Send 'go' if we are in a mode where machine should play. */
4995     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4996         (gameMode == TwoMachinesPlay   ||
4997 #if ZIPPY
4998          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4999 #endif
5000          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5001         SendToProgram("go\n", cps);
5002   if (appData.debugMode) {
5003     fprintf(debugFP, "(extra)\n");
5004   }
5005     }
5006     setboardSpoiledMachineBlack = 0;
5007 }
5008
5009 void
5010 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5011 {
5012     char user_move[MSG_SIZ];
5013     char suffix[4];
5014
5015     if(gameInfo.variant == VariantSChess && promoChar) {
5016         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5017         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5018     } else suffix[0] = NULLCHAR;
5019
5020     switch (moveType) {
5021       default:
5022         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5023                 (int)moveType, fromX, fromY, toX, toY);
5024         DisplayError(user_move + strlen("say "), 0);
5025         break;
5026       case WhiteKingSideCastle:
5027       case BlackKingSideCastle:
5028       case WhiteQueenSideCastleWild:
5029       case BlackQueenSideCastleWild:
5030       /* PUSH Fabien */
5031       case WhiteHSideCastleFR:
5032       case BlackHSideCastleFR:
5033       /* POP Fabien */
5034         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5035         break;
5036       case WhiteQueenSideCastle:
5037       case BlackQueenSideCastle:
5038       case WhiteKingSideCastleWild:
5039       case BlackKingSideCastleWild:
5040       /* PUSH Fabien */
5041       case WhiteASideCastleFR:
5042       case BlackASideCastleFR:
5043       /* POP Fabien */
5044         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5045         break;
5046       case WhiteNonPromotion:
5047       case BlackNonPromotion:
5048         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5049         break;
5050       case WhitePromotion:
5051       case BlackPromotion:
5052         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5053           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5055                 PieceToChar(WhiteFerz));
5056         else if(gameInfo.variant == VariantGreat)
5057           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5058                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5059                 PieceToChar(WhiteMan));
5060         else
5061           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5062                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5063                 promoChar);
5064         break;
5065       case WhiteDrop:
5066       case BlackDrop:
5067       drop:
5068         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5069                  ToUpper(PieceToChar((ChessSquare) fromX)),
5070                  AAA + toX, ONE + toY);
5071         break;
5072       case IllegalMove:  /* could be a variant we don't quite understand */
5073         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5074       case NormalMove:
5075       case WhiteCapturesEnPassant:
5076       case BlackCapturesEnPassant:
5077         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5078                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5079         break;
5080     }
5081     SendToICS(user_move);
5082     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5083         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5084 }
5085
5086 void
5087 UploadGameEvent ()
5088 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5089     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5090     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5091     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5092       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5093       return;
5094     }
5095     if(gameMode != IcsExamining) { // is this ever not the case?
5096         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5097
5098         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5099           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5100         } else { // on FICS we must first go to general examine mode
5101           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5102         }
5103         if(gameInfo.variant != VariantNormal) {
5104             // try figure out wild number, as xboard names are not always valid on ICS
5105             for(i=1; i<=36; i++) {
5106               snprintf(buf, MSG_SIZ, "wild/%d", i);
5107                 if(StringToVariant(buf) == gameInfo.variant) break;
5108             }
5109             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5110             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5111             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5112         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5113         SendToICS(ics_prefix);
5114         SendToICS(buf);
5115         if(startedFromSetupPosition || backwardMostMove != 0) {
5116           fen = PositionToFEN(backwardMostMove, NULL);
5117           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5118             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5119             SendToICS(buf);
5120           } else { // FICS: everything has to set by separate bsetup commands
5121             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5122             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5123             SendToICS(buf);
5124             if(!WhiteOnMove(backwardMostMove)) {
5125                 SendToICS("bsetup tomove black\n");
5126             }
5127             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5128             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5129             SendToICS(buf);
5130             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5131             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5132             SendToICS(buf);
5133             i = boards[backwardMostMove][EP_STATUS];
5134             if(i >= 0) { // set e.p.
5135               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5136                 SendToICS(buf);
5137             }
5138             bsetup++;
5139           }
5140         }
5141       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5142             SendToICS("bsetup done\n"); // switch to normal examining.
5143     }
5144     for(i = backwardMostMove; i<last; i++) {
5145         char buf[20];
5146         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5147         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5148             int len = strlen(moveList[i]);
5149             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5150             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5151         }
5152         SendToICS(buf);
5153     }
5154     SendToICS(ics_prefix);
5155     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5156 }
5157
5158 void
5159 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5160 {
5161     if (rf == DROP_RANK) {
5162       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5163       sprintf(move, "%c@%c%c\n",
5164                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5165     } else {
5166         if (promoChar == 'x' || promoChar == NULLCHAR) {
5167           sprintf(move, "%c%c%c%c\n",
5168                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5169         } else {
5170             sprintf(move, "%c%c%c%c%c\n",
5171                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5172         }
5173     }
5174 }
5175
5176 void
5177 ProcessICSInitScript (FILE *f)
5178 {
5179     char buf[MSG_SIZ];
5180
5181     while (fgets(buf, MSG_SIZ, f)) {
5182         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5183     }
5184
5185     fclose(f);
5186 }
5187
5188
5189 static int lastX, lastY, selectFlag, dragging;
5190
5191 void
5192 Sweep (int step)
5193 {
5194     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5195     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5196     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5197     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5198     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5199     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5200     do {
5201         promoSweep -= step;
5202         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5203         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5204         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5205         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5206         if(!step) step = -1;
5207     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5208             appData.testLegality && (promoSweep == king ||
5209             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5210     if(toX >= 0) {
5211         int victim = boards[currentMove][toY][toX];
5212         boards[currentMove][toY][toX] = promoSweep;
5213         DrawPosition(FALSE, boards[currentMove]);
5214         boards[currentMove][toY][toX] = victim;
5215     } else
5216     ChangeDragPiece(promoSweep);
5217 }
5218
5219 int
5220 PromoScroll (int x, int y)
5221 {
5222   int step = 0;
5223
5224   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5225   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5226   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5227   if(!step) return FALSE;
5228   lastX = x; lastY = y;
5229   if((promoSweep < BlackPawn) == flipView) step = -step;
5230   if(step > 0) selectFlag = 1;
5231   if(!selectFlag) Sweep(step);
5232   return FALSE;
5233 }
5234
5235 void
5236 NextPiece (int step)
5237 {
5238     ChessSquare piece = boards[currentMove][toY][toX];
5239     do {
5240         pieceSweep -= step;
5241         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5242         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5243         if(!step) step = -1;
5244     } while(PieceToChar(pieceSweep) == '.');
5245     boards[currentMove][toY][toX] = pieceSweep;
5246     DrawPosition(FALSE, boards[currentMove]);
5247     boards[currentMove][toY][toX] = piece;
5248 }
5249 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5250 void
5251 AlphaRank (char *move, int n)
5252 {
5253 //    char *p = move, c; int x, y;
5254
5255     if (appData.debugMode) {
5256         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5257     }
5258
5259     if(move[1]=='*' &&
5260        move[2]>='0' && move[2]<='9' &&
5261        move[3]>='a' && move[3]<='x'    ) {
5262         move[1] = '@';
5263         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5264         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5265     } else
5266     if(move[0]>='0' && move[0]<='9' &&
5267        move[1]>='a' && move[1]<='x' &&
5268        move[2]>='0' && move[2]<='9' &&
5269        move[3]>='a' && move[3]<='x'    ) {
5270         /* input move, Shogi -> normal */
5271         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5272         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
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[1]=='@' &&
5277        move[3]>='0' && move[3]<='9' &&
5278        move[2]>='a' && move[2]<='x'    ) {
5279         move[1] = '*';
5280         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5281         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5282     } else
5283     if(
5284        move[0]>='a' && move[0]<='x' &&
5285        move[3]>='0' && move[3]<='9' &&
5286        move[2]>='a' && move[2]<='x'    ) {
5287          /* output move, normal -> Shogi */
5288         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5289         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5290         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5291         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5292         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5293     }
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "   out = '%s'\n", move);
5296     }
5297 }
5298
5299 char yy_textstr[8000];
5300
5301 /* Parser for moves from gnuchess, ICS, or user typein box */
5302 Boolean
5303 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5304 {
5305     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5306
5307     switch (*moveType) {
5308       case WhitePromotion:
5309       case BlackPromotion:
5310       case WhiteNonPromotion:
5311       case BlackNonPromotion:
5312       case NormalMove:
5313       case WhiteCapturesEnPassant:
5314       case BlackCapturesEnPassant:
5315       case WhiteKingSideCastle:
5316       case WhiteQueenSideCastle:
5317       case BlackKingSideCastle:
5318       case BlackQueenSideCastle:
5319       case WhiteKingSideCastleWild:
5320       case WhiteQueenSideCastleWild:
5321       case BlackKingSideCastleWild:
5322       case BlackQueenSideCastleWild:
5323       /* Code added by Tord: */
5324       case WhiteHSideCastleFR:
5325       case WhiteASideCastleFR:
5326       case BlackHSideCastleFR:
5327       case BlackASideCastleFR:
5328       /* End of code added by Tord */
5329       case IllegalMove:         /* bug or odd chess variant */
5330         *fromX = currentMoveString[0] - AAA;
5331         *fromY = currentMoveString[1] - ONE;
5332         *toX = currentMoveString[2] - AAA;
5333         *toY = currentMoveString[3] - ONE;
5334         *promoChar = currentMoveString[4];
5335         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5336             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5337     if (appData.debugMode) {
5338         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5339     }
5340             *fromX = *fromY = *toX = *toY = 0;
5341             return FALSE;
5342         }
5343         if (appData.testLegality) {
5344           return (*moveType != IllegalMove);
5345         } else {
5346           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5347                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5348         }
5349
5350       case WhiteDrop:
5351       case BlackDrop:
5352         *fromX = *moveType == WhiteDrop ?
5353           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5354           (int) CharToPiece(ToLower(currentMoveString[0]));
5355         *fromY = DROP_RANK;
5356         *toX = currentMoveString[2] - AAA;
5357         *toY = currentMoveString[3] - ONE;
5358         *promoChar = NULLCHAR;
5359         return TRUE;
5360
5361       case AmbiguousMove:
5362       case ImpossibleMove:
5363       case EndOfFile:
5364       case ElapsedTime:
5365       case Comment:
5366       case PGNTag:
5367       case NAG:
5368       case WhiteWins:
5369       case BlackWins:
5370       case GameIsDrawn:
5371       default:
5372     if (appData.debugMode) {
5373         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5374     }
5375         /* bug? */
5376         *fromX = *fromY = *toX = *toY = 0;
5377         *promoChar = NULLCHAR;
5378         return FALSE;
5379     }
5380 }
5381
5382 Boolean pushed = FALSE;
5383 char *lastParseAttempt;
5384
5385 void
5386 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5387 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5388   int fromX, fromY, toX, toY; char promoChar;
5389   ChessMove moveType;
5390   Boolean valid;
5391   int nr = 0;
5392
5393   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5394     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5395     pushed = TRUE;
5396   }
5397   endPV = forwardMostMove;
5398   do {
5399     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5400     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5401     lastParseAttempt = pv;
5402     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5403     if(!valid && nr == 0 &&
5404        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5405         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5406         // Hande case where played move is different from leading PV move
5407         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5408         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5409         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5410         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5411           endPV += 2; // if position different, keep this
5412           moveList[endPV-1][0] = fromX + AAA;
5413           moveList[endPV-1][1] = fromY + ONE;
5414           moveList[endPV-1][2] = toX + AAA;
5415           moveList[endPV-1][3] = toY + ONE;
5416           parseList[endPV-1][0] = NULLCHAR;
5417           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5418         }
5419       }
5420     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5421     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5422     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5423     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5424         valid++; // allow comments in PV
5425         continue;
5426     }
5427     nr++;
5428     if(endPV+1 > framePtr) break; // no space, truncate
5429     if(!valid) break;
5430     endPV++;
5431     CopyBoard(boards[endPV], boards[endPV-1]);
5432     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5433     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5434     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5435     CoordsToAlgebraic(boards[endPV - 1],
5436                              PosFlags(endPV - 1),
5437                              fromY, fromX, toY, toX, promoChar,
5438                              parseList[endPV - 1]);
5439   } while(valid);
5440   if(atEnd == 2) return; // used hidden, for PV conversion
5441   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5442   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5443   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5444                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5445   DrawPosition(TRUE, boards[currentMove]);
5446 }
5447
5448 int
5449 MultiPV (ChessProgramState *cps)
5450 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5451         int i;
5452         for(i=0; i<cps->nrOptions; i++)
5453             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5454                 return i;
5455         return -1;
5456 }
5457
5458 Boolean
5459 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5460 {
5461         int startPV, multi, lineStart, origIndex = index;
5462         char *p, buf2[MSG_SIZ];
5463         ChessProgramState *cps = (pane ? &second : &first);
5464
5465         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5466         lastX = x; lastY = y;
5467         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5468         lineStart = startPV = index;
5469         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5470         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5471         index = startPV;
5472         do{ while(buf[index] && buf[index] != '\n') index++;
5473         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5474         buf[index] = 0;
5475         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5476                 int n = cps->option[multi].value;
5477                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5478                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5479                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5480                 cps->option[multi].value = n;
5481                 *start = *end = 0;
5482                 return FALSE;
5483         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5484                 ExcludeClick(origIndex - lineStart);
5485                 return FALSE;
5486         }
5487         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5488         *start = startPV; *end = index-1;
5489         return TRUE;
5490 }
5491
5492 char *
5493 PvToSAN (char *pv)
5494 {
5495         static char buf[10*MSG_SIZ];
5496         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5497         *buf = NULLCHAR;
5498         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5499         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5500         for(i = forwardMostMove; i<endPV; i++){
5501             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5502             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5503             k += strlen(buf+k);
5504         }
5505         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5506         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5507         endPV = savedEnd;
5508         return buf;
5509 }
5510
5511 Boolean
5512 LoadPV (int x, int y)
5513 { // called on right mouse click to load PV
5514   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5515   lastX = x; lastY = y;
5516   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5517   return TRUE;
5518 }
5519
5520 void
5521 UnLoadPV ()
5522 {
5523   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5524   if(endPV < 0) return;
5525   if(appData.autoCopyPV) CopyFENToClipboard();
5526   endPV = -1;
5527   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5528         Boolean saveAnimate = appData.animate;
5529         if(pushed) {
5530             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5531                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5532             } else storedGames--; // abandon shelved tail of original game
5533         }
5534         pushed = FALSE;
5535         forwardMostMove = currentMove;
5536         currentMove = oldFMM;
5537         appData.animate = FALSE;
5538         ToNrEvent(forwardMostMove);
5539         appData.animate = saveAnimate;
5540   }
5541   currentMove = forwardMostMove;
5542   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5543   ClearPremoveHighlights();
5544   DrawPosition(TRUE, boards[currentMove]);
5545 }
5546
5547 void
5548 MovePV (int x, int y, int h)
5549 { // step through PV based on mouse coordinates (called on mouse move)
5550   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5551
5552   // we must somehow check if right button is still down (might be released off board!)
5553   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5554   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5555   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5556   if(!step) return;
5557   lastX = x; lastY = y;
5558
5559   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5560   if(endPV < 0) return;
5561   if(y < margin) step = 1; else
5562   if(y > h - margin) step = -1;
5563   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5564   currentMove += step;
5565   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5566   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5567                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5568   DrawPosition(FALSE, boards[currentMove]);
5569 }
5570
5571
5572 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5573 // All positions will have equal probability, but the current method will not provide a unique
5574 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5575 #define DARK 1
5576 #define LITE 2
5577 #define ANY 3
5578
5579 int squaresLeft[4];
5580 int piecesLeft[(int)BlackPawn];
5581 int seed, nrOfShuffles;
5582
5583 void
5584 GetPositionNumber ()
5585 {       // sets global variable seed
5586         int i;
5587
5588         seed = appData.defaultFrcPosition;
5589         if(seed < 0) { // randomize based on time for negative FRC position numbers
5590                 for(i=0; i<50; i++) seed += random();
5591                 seed = random() ^ random() >> 8 ^ random() << 8;
5592                 if(seed<0) seed = -seed;
5593         }
5594 }
5595
5596 int
5597 put (Board board, int pieceType, int rank, int n, int shade)
5598 // put the piece on the (n-1)-th empty squares of the given shade
5599 {
5600         int i;
5601
5602         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5603                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5604                         board[rank][i] = (ChessSquare) pieceType;
5605                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5606                         squaresLeft[ANY]--;
5607                         piecesLeft[pieceType]--;
5608                         return i;
5609                 }
5610         }
5611         return -1;
5612 }
5613
5614
5615 void
5616 AddOnePiece (Board board, int pieceType, int rank, int shade)
5617 // calculate where the next piece goes, (any empty square), and put it there
5618 {
5619         int i;
5620
5621         i = seed % squaresLeft[shade];
5622         nrOfShuffles *= squaresLeft[shade];
5623         seed /= squaresLeft[shade];
5624         put(board, pieceType, rank, i, shade);
5625 }
5626
5627 void
5628 AddTwoPieces (Board board, int pieceType, int rank)
5629 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5630 {
5631         int i, n=squaresLeft[ANY], j=n-1, k;
5632
5633         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5634         i = seed % k;  // pick one
5635         nrOfShuffles *= k;
5636         seed /= k;
5637         while(i >= j) i -= j--;
5638         j = n - 1 - j; i += j;
5639         put(board, pieceType, rank, j, ANY);
5640         put(board, pieceType, rank, i, ANY);
5641 }
5642
5643 void
5644 SetUpShuffle (Board board, int number)
5645 {
5646         int i, p, first=1;
5647
5648         GetPositionNumber(); nrOfShuffles = 1;
5649
5650         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5651         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5652         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5653
5654         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5655
5656         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5657             p = (int) board[0][i];
5658             if(p < (int) BlackPawn) piecesLeft[p] ++;
5659             board[0][i] = EmptySquare;
5660         }
5661
5662         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5663             // shuffles restricted to allow normal castling put KRR first
5664             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5665                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5666             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5667                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5668             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5669                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5670             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5671                 put(board, WhiteRook, 0, 0, ANY);
5672             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5673         }
5674
5675         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5676             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5677             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5678                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5679                 while(piecesLeft[p] >= 2) {
5680                     AddOnePiece(board, p, 0, LITE);
5681                     AddOnePiece(board, p, 0, DARK);
5682                 }
5683                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5684             }
5685
5686         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5687             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5688             // but we leave King and Rooks for last, to possibly obey FRC restriction
5689             if(p == (int)WhiteRook) continue;
5690             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5691             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5692         }
5693
5694         // now everything is placed, except perhaps King (Unicorn) and Rooks
5695
5696         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5697             // Last King gets castling rights
5698             while(piecesLeft[(int)WhiteUnicorn]) {
5699                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5700                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5701             }
5702
5703             while(piecesLeft[(int)WhiteKing]) {
5704                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5705                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5706             }
5707
5708
5709         } else {
5710             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5711             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5712         }
5713
5714         // Only Rooks can be left; simply place them all
5715         while(piecesLeft[(int)WhiteRook]) {
5716                 i = put(board, WhiteRook, 0, 0, ANY);
5717                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5718                         if(first) {
5719                                 first=0;
5720                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5721                         }
5722                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5723                 }
5724         }
5725         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5726             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5727         }
5728
5729         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5730 }
5731
5732 int
5733 SetCharTable (char *table, const char * map)
5734 /* [HGM] moved here from winboard.c because of its general usefulness */
5735 /*       Basically a safe strcpy that uses the last character as King */
5736 {
5737     int result = FALSE; int NrPieces;
5738
5739     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5740                     && NrPieces >= 12 && !(NrPieces&1)) {
5741         int i; /* [HGM] Accept even length from 12 to 34 */
5742
5743         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5744         for( i=0; i<NrPieces/2-1; i++ ) {
5745             table[i] = map[i];
5746             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5747         }
5748         table[(int) WhiteKing]  = map[NrPieces/2-1];
5749         table[(int) BlackKing]  = map[NrPieces-1];
5750
5751         result = TRUE;
5752     }
5753
5754     return result;
5755 }
5756
5757 void
5758 Prelude (Board board)
5759 {       // [HGM] superchess: random selection of exo-pieces
5760         int i, j, k; ChessSquare p;
5761         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5762
5763         GetPositionNumber(); // use FRC position number
5764
5765         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5766             SetCharTable(pieceToChar, appData.pieceToCharTable);
5767             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5768                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5769         }
5770
5771         j = seed%4;                 seed /= 4;
5772         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5773         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5774         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5775         j = seed%3 + (seed%3 >= j); seed /= 3;
5776         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5777         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5778         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5779         j = seed%3;                 seed /= 3;
5780         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5781         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5782         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5783         j = seed%2 + (seed%2 >= j); seed /= 2;
5784         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5785         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5786         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5787         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5788         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5789         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5790         put(board, exoPieces[0],    0, 0, ANY);
5791         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5792 }
5793
5794 void
5795 InitPosition (int redraw)
5796 {
5797     ChessSquare (* pieces)[BOARD_FILES];
5798     int i, j, pawnRow, overrule,
5799     oldx = gameInfo.boardWidth,
5800     oldy = gameInfo.boardHeight,
5801     oldh = gameInfo.holdingsWidth;
5802     static int oldv;
5803
5804     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5805
5806     /* [AS] Initialize pv info list [HGM] and game status */
5807     {
5808         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5809             pvInfoList[i].depth = 0;
5810             boards[i][EP_STATUS] = EP_NONE;
5811             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5812         }
5813
5814         initialRulePlies = 0; /* 50-move counter start */
5815
5816         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5817         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5818     }
5819
5820
5821     /* [HGM] logic here is completely changed. In stead of full positions */
5822     /* the initialized data only consist of the two backranks. The switch */
5823     /* selects which one we will use, which is than copied to the Board   */
5824     /* initialPosition, which for the rest is initialized by Pawns and    */
5825     /* empty squares. This initial position is then copied to boards[0],  */
5826     /* possibly after shuffling, so that it remains available.            */
5827
5828     gameInfo.holdingsWidth = 0; /* default board sizes */
5829     gameInfo.boardWidth    = 8;
5830     gameInfo.boardHeight   = 8;
5831     gameInfo.holdingsSize  = 0;
5832     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5833     for(i=0; i<BOARD_FILES-2; i++)
5834       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5835     initialPosition[EP_STATUS] = EP_NONE;
5836     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5837     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5838          SetCharTable(pieceNickName, appData.pieceNickNames);
5839     else SetCharTable(pieceNickName, "............");
5840     pieces = FIDEArray;
5841
5842     switch (gameInfo.variant) {
5843     case VariantFischeRandom:
5844       shuffleOpenings = TRUE;
5845     default:
5846       break;
5847     case VariantShatranj:
5848       pieces = ShatranjArray;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5851       break;
5852     case VariantMakruk:
5853       pieces = makrukArray;
5854       nrCastlingRights = 0;
5855       startedFromSetupPosition = TRUE;
5856       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5857       break;
5858     case VariantTwoKings:
5859       pieces = twoKingsArray;
5860       break;
5861     case VariantGrand:
5862       pieces = GrandArray;
5863       nrCastlingRights = 0;
5864       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5865       gameInfo.boardWidth = 10;
5866       gameInfo.boardHeight = 10;
5867       gameInfo.holdingsSize = 7;
5868       break;
5869     case VariantCapaRandom:
5870       shuffleOpenings = TRUE;
5871     case VariantCapablanca:
5872       pieces = CapablancaArray;
5873       gameInfo.boardWidth = 10;
5874       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5875       break;
5876     case VariantGothic:
5877       pieces = GothicArray;
5878       gameInfo.boardWidth = 10;
5879       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5880       break;
5881     case VariantSChess:
5882       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5883       gameInfo.holdingsSize = 7;
5884       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5885       break;
5886     case VariantJanus:
5887       pieces = JanusArray;
5888       gameInfo.boardWidth = 10;
5889       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5890       nrCastlingRights = 6;
5891         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5892         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5893         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5894         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5895         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5896         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5897       break;
5898     case VariantFalcon:
5899       pieces = FalconArray;
5900       gameInfo.boardWidth = 10;
5901       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5902       break;
5903     case VariantXiangqi:
5904       pieces = XiangqiArray;
5905       gameInfo.boardWidth  = 9;
5906       gameInfo.boardHeight = 10;
5907       nrCastlingRights = 0;
5908       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5909       break;
5910     case VariantShogi:
5911       pieces = ShogiArray;
5912       gameInfo.boardWidth  = 9;
5913       gameInfo.boardHeight = 9;
5914       gameInfo.holdingsSize = 7;
5915       nrCastlingRights = 0;
5916       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5917       break;
5918     case VariantCourier:
5919       pieces = CourierArray;
5920       gameInfo.boardWidth  = 12;
5921       nrCastlingRights = 0;
5922       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5923       break;
5924     case VariantKnightmate:
5925       pieces = KnightmateArray;
5926       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5927       break;
5928     case VariantSpartan:
5929       pieces = SpartanArray;
5930       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5931       break;
5932     case VariantFairy:
5933       pieces = fairyArray;
5934       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5935       break;
5936     case VariantGreat:
5937       pieces = GreatArray;
5938       gameInfo.boardWidth = 10;
5939       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5940       gameInfo.holdingsSize = 8;
5941       break;
5942     case VariantSuper:
5943       pieces = FIDEArray;
5944       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5945       gameInfo.holdingsSize = 8;
5946       startedFromSetupPosition = TRUE;
5947       break;
5948     case VariantCrazyhouse:
5949     case VariantBughouse:
5950       pieces = FIDEArray;
5951       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5952       gameInfo.holdingsSize = 5;
5953       break;
5954     case VariantWildCastle:
5955       pieces = FIDEArray;
5956       /* !!?shuffle with kings guaranteed to be on d or e file */
5957       shuffleOpenings = 1;
5958       break;
5959     case VariantNoCastle:
5960       pieces = FIDEArray;
5961       nrCastlingRights = 0;
5962       /* !!?unconstrained back-rank shuffle */
5963       shuffleOpenings = 1;
5964       break;
5965     }
5966
5967     overrule = 0;
5968     if(appData.NrFiles >= 0) {
5969         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5970         gameInfo.boardWidth = appData.NrFiles;
5971     }
5972     if(appData.NrRanks >= 0) {
5973         gameInfo.boardHeight = appData.NrRanks;
5974     }
5975     if(appData.holdingsSize >= 0) {
5976         i = appData.holdingsSize;
5977         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5978         gameInfo.holdingsSize = i;
5979     }
5980     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5981     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5982         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5983
5984     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5985     if(pawnRow < 1) pawnRow = 1;
5986     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5987
5988     /* User pieceToChar list overrules defaults */
5989     if(appData.pieceToCharTable != NULL)
5990         SetCharTable(pieceToChar, appData.pieceToCharTable);
5991
5992     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5993
5994         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5995             s = (ChessSquare) 0; /* account holding counts in guard band */
5996         for( i=0; i<BOARD_HEIGHT; i++ )
5997             initialPosition[i][j] = s;
5998
5999         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6000         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6001         initialPosition[pawnRow][j] = WhitePawn;
6002         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6003         if(gameInfo.variant == VariantXiangqi) {
6004             if(j&1) {
6005                 initialPosition[pawnRow][j] =
6006                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6007                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6008                    initialPosition[2][j] = WhiteCannon;
6009                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6010                 }
6011             }
6012         }
6013         if(gameInfo.variant == VariantGrand) {
6014             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6015                initialPosition[0][j] = WhiteRook;
6016                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6017             }
6018         }
6019         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6020     }
6021     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6022
6023             j=BOARD_LEFT+1;
6024             initialPosition[1][j] = WhiteBishop;
6025             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6026             j=BOARD_RGHT-2;
6027             initialPosition[1][j] = WhiteRook;
6028             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6029     }
6030
6031     if( nrCastlingRights == -1) {
6032         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6033         /*       This sets default castling rights from none to normal corners   */
6034         /* Variants with other castling rights must set them themselves above    */
6035         nrCastlingRights = 6;
6036
6037         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6038         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6039         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6040         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6041         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6042         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6043      }
6044
6045      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6046      if(gameInfo.variant == VariantGreat) { // promotion commoners
6047         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6048         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6049         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6050         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6051      }
6052      if( gameInfo.variant == VariantSChess ) {
6053       initialPosition[1][0] = BlackMarshall;
6054       initialPosition[2][0] = BlackAngel;
6055       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6056       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6057       initialPosition[1][1] = initialPosition[2][1] = 
6058       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6059      }
6060   if (appData.debugMode) {
6061     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6062   }
6063     if(shuffleOpenings) {
6064         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6065         startedFromSetupPosition = TRUE;
6066     }
6067     if(startedFromPositionFile) {
6068       /* [HGM] loadPos: use PositionFile for every new game */
6069       CopyBoard(initialPosition, filePosition);
6070       for(i=0; i<nrCastlingRights; i++)
6071           initialRights[i] = filePosition[CASTLING][i];
6072       startedFromSetupPosition = TRUE;
6073     }
6074
6075     CopyBoard(boards[0], initialPosition);
6076
6077     if(oldx != gameInfo.boardWidth ||
6078        oldy != gameInfo.boardHeight ||
6079        oldv != gameInfo.variant ||
6080        oldh != gameInfo.holdingsWidth
6081                                          )
6082             InitDrawingSizes(-2 ,0);
6083
6084     oldv = gameInfo.variant;
6085     if (redraw)
6086       DrawPosition(TRUE, boards[currentMove]);
6087 }
6088
6089 void
6090 SendBoard (ChessProgramState *cps, int moveNum)
6091 {
6092     char message[MSG_SIZ];
6093
6094     if (cps->useSetboard) {
6095       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6096       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6097       SendToProgram(message, cps);
6098       free(fen);
6099
6100     } else {
6101       ChessSquare *bp;
6102       int i, j, left=0, right=BOARD_WIDTH;
6103       /* Kludge to set black to move, avoiding the troublesome and now
6104        * deprecated "black" command.
6105        */
6106       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6107         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6108
6109       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6110
6111       SendToProgram("edit\n", cps);
6112       SendToProgram("#\n", cps);
6113       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6114         bp = &boards[moveNum][i][left];
6115         for (j = left; j < right; j++, bp++) {
6116           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6117           if ((int) *bp < (int) BlackPawn) {
6118             if(j == BOARD_RGHT+1)
6119                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6120             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6121             if(message[0] == '+' || message[0] == '~') {
6122               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6123                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6124                         AAA + j, ONE + i);
6125             }
6126             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6127                 message[1] = BOARD_RGHT   - 1 - j + '1';
6128                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6129             }
6130             SendToProgram(message, cps);
6131           }
6132         }
6133       }
6134
6135       SendToProgram("c\n", cps);
6136       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6137         bp = &boards[moveNum][i][left];
6138         for (j = left; j < right; j++, bp++) {
6139           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6140           if (((int) *bp != (int) EmptySquare)
6141               && ((int) *bp >= (int) BlackPawn)) {
6142             if(j == BOARD_LEFT-2)
6143                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6144             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6145                     AAA + j, ONE + i);
6146             if(message[0] == '+' || message[0] == '~') {
6147               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6148                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6149                         AAA + j, ONE + i);
6150             }
6151             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6152                 message[1] = BOARD_RGHT   - 1 - j + '1';
6153                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6154             }
6155             SendToProgram(message, cps);
6156           }
6157         }
6158       }
6159
6160       SendToProgram(".\n", cps);
6161     }
6162     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6163 }
6164
6165 char exclusionHeader[MSG_SIZ];
6166 int exCnt, excludePtr;
6167 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6168 static Exclusion excluTab[200];
6169 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6170
6171 static void
6172 WriteMap (int s)
6173 {
6174     int j;
6175     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6176     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6177 }
6178
6179 static void
6180 ClearMap ()
6181 {
6182     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6183     excludePtr = 24; exCnt = 0;
6184     WriteMap(0);
6185 }
6186
6187 static void
6188 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6189 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6190     char buf[2*MOVE_LEN], *p;
6191     Exclusion *e = excluTab;
6192     int i;
6193     for(i=0; i<exCnt; i++)
6194         if(e[i].ff == fromX && e[i].fr == fromY &&
6195            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6196     if(i == exCnt) { // was not in exclude list; add it
6197         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6198         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6199             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6200             return; // abort
6201         }
6202         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6203         excludePtr++; e[i].mark = excludePtr++;
6204         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6205         exCnt++;
6206     }
6207     exclusionHeader[e[i].mark] = state;
6208 }
6209
6210 static int
6211 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6212 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6213     char buf[MSG_SIZ];
6214     int j, k;
6215     ChessMove moveType;
6216     if((signed char)promoChar == -1) { // kludge to indicate best move
6217         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6218             return 1; // if unparsable, abort
6219     }
6220     // update exclusion map (resolving toggle by consulting existing state)
6221     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6222     j = k%8; k >>= 3;
6223     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6224     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6225          excludeMap[k] |=   1<<j;
6226     else excludeMap[k] &= ~(1<<j);
6227     // update header
6228     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6229     // inform engine
6230     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6231     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6232     SendToBoth(buf);
6233     return (state == '+');
6234 }
6235
6236 static void
6237 ExcludeClick (int index)
6238 {
6239     int i, j;
6240     Exclusion *e = excluTab;
6241     if(index < 25) { // none, best or tail clicked
6242         if(index < 13) { // none: include all
6243             WriteMap(0); // clear map
6244             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6245             SendToBoth("include all\n"); // and inform engine
6246         } else if(index > 18) { // tail
6247             if(exclusionHeader[19] == '-') { // tail was excluded
6248                 SendToBoth("include all\n");
6249                 WriteMap(0); // clear map completely
6250                 // now re-exclude selected moves
6251                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6252                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6253             } else { // tail was included or in mixed state
6254                 SendToBoth("exclude all\n");
6255                 WriteMap(0xFF); // fill map completely
6256                 // now re-include selected moves
6257                 j = 0; // count them
6258                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6259                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6260                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6261             }
6262         } else { // best
6263             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6264         }
6265     } else {
6266         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6267             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6268             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6269             break;
6270         }
6271     }
6272 }
6273
6274 ChessSquare
6275 DefaultPromoChoice (int white)
6276 {
6277     ChessSquare result;
6278     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6279         result = WhiteFerz; // no choice
6280     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6281         result= WhiteKing; // in Suicide Q is the last thing we want
6282     else if(gameInfo.variant == VariantSpartan)
6283         result = white ? WhiteQueen : WhiteAngel;
6284     else result = WhiteQueen;
6285     if(!white) result = WHITE_TO_BLACK result;
6286     return result;
6287 }
6288
6289 static int autoQueen; // [HGM] oneclick
6290
6291 int
6292 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6293 {
6294     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6295     /* [HGM] add Shogi promotions */
6296     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6297     ChessSquare piece;
6298     ChessMove moveType;
6299     Boolean premove;
6300
6301     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6302     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6303
6304     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6305       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6306         return FALSE;
6307
6308     piece = boards[currentMove][fromY][fromX];
6309     if(gameInfo.variant == VariantShogi) {
6310         promotionZoneSize = BOARD_HEIGHT/3;
6311         highestPromotingPiece = (int)WhiteFerz;
6312     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6313         promotionZoneSize = 3;
6314     }
6315
6316     // Treat Lance as Pawn when it is not representing Amazon
6317     if(gameInfo.variant != VariantSuper) {
6318         if(piece == WhiteLance) piece = WhitePawn; else
6319         if(piece == BlackLance) piece = BlackPawn;
6320     }
6321
6322     // next weed out all moves that do not touch the promotion zone at all
6323     if((int)piece >= BlackPawn) {
6324         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6325              return FALSE;
6326         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6327     } else {
6328         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6329            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6330     }
6331
6332     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6333
6334     // weed out mandatory Shogi promotions
6335     if(gameInfo.variant == VariantShogi) {
6336         if(piece >= BlackPawn) {
6337             if(toY == 0 && piece == BlackPawn ||
6338                toY == 0 && piece == BlackQueen ||
6339                toY <= 1 && piece == BlackKnight) {
6340                 *promoChoice = '+';
6341                 return FALSE;
6342             }
6343         } else {
6344             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6345                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6346                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6347                 *promoChoice = '+';
6348                 return FALSE;
6349             }
6350         }
6351     }
6352
6353     // weed out obviously illegal Pawn moves
6354     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6355         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6356         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6357         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6358         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6359         // note we are not allowed to test for valid (non-)capture, due to premove
6360     }
6361
6362     // we either have a choice what to promote to, or (in Shogi) whether to promote
6363     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6364         *promoChoice = PieceToChar(BlackFerz);  // no choice
6365         return FALSE;
6366     }
6367     // no sense asking what we must promote to if it is going to explode...
6368     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6369         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6370         return FALSE;
6371     }
6372     // give caller the default choice even if we will not make it
6373     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6374     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6375     if(        sweepSelect && gameInfo.variant != VariantGreat
6376                            && gameInfo.variant != VariantGrand
6377                            && gameInfo.variant != VariantSuper) return FALSE;
6378     if(autoQueen) return FALSE; // predetermined
6379
6380     // suppress promotion popup on illegal moves that are not premoves
6381     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6382               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6383     if(appData.testLegality && !premove) {
6384         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6385                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6386         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6387             return FALSE;
6388     }
6389
6390     return TRUE;
6391 }
6392
6393 int
6394 InPalace (int row, int column)
6395 {   /* [HGM] for Xiangqi */
6396     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6397          column < (BOARD_WIDTH + 4)/2 &&
6398          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6399     return FALSE;
6400 }
6401
6402 int
6403 PieceForSquare (int x, int y)
6404 {
6405   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6406      return -1;
6407   else
6408      return boards[currentMove][y][x];
6409 }
6410
6411 int
6412 OKToStartUserMove (int x, int y)
6413 {
6414     ChessSquare from_piece;
6415     int white_piece;
6416
6417     if (matchMode) return FALSE;
6418     if (gameMode == EditPosition) return TRUE;
6419
6420     if (x >= 0 && y >= 0)
6421       from_piece = boards[currentMove][y][x];
6422     else
6423       from_piece = EmptySquare;
6424
6425     if (from_piece == EmptySquare) return FALSE;
6426
6427     white_piece = (int)from_piece >= (int)WhitePawn &&
6428       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6429
6430     switch (gameMode) {
6431       case AnalyzeFile:
6432       case TwoMachinesPlay:
6433       case EndOfGame:
6434         return FALSE;
6435
6436       case IcsObserving:
6437       case IcsIdle:
6438         return FALSE;
6439
6440       case MachinePlaysWhite:
6441       case IcsPlayingBlack:
6442         if (appData.zippyPlay) return FALSE;
6443         if (white_piece) {
6444             DisplayMoveError(_("You are playing Black"));
6445             return FALSE;
6446         }
6447         break;
6448
6449       case MachinePlaysBlack:
6450       case IcsPlayingWhite:
6451         if (appData.zippyPlay) return FALSE;
6452         if (!white_piece) {
6453             DisplayMoveError(_("You are playing White"));
6454             return FALSE;
6455         }
6456         break;
6457
6458       case PlayFromGameFile:
6459             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6460       case EditGame:
6461         if (!white_piece && WhiteOnMove(currentMove)) {
6462             DisplayMoveError(_("It is White's turn"));
6463             return FALSE;
6464         }
6465         if (white_piece && !WhiteOnMove(currentMove)) {
6466             DisplayMoveError(_("It is Black's turn"));
6467             return FALSE;
6468         }
6469         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6470             /* Editing correspondence game history */
6471             /* Could disallow this or prompt for confirmation */
6472             cmailOldMove = -1;
6473         }
6474         break;
6475
6476       case BeginningOfGame:
6477         if (appData.icsActive) return FALSE;
6478         if (!appData.noChessProgram) {
6479             if (!white_piece) {
6480                 DisplayMoveError(_("You are playing White"));
6481                 return FALSE;
6482             }
6483         }
6484         break;
6485
6486       case Training:
6487         if (!white_piece && WhiteOnMove(currentMove)) {
6488             DisplayMoveError(_("It is White's turn"));
6489             return FALSE;
6490         }
6491         if (white_piece && !WhiteOnMove(currentMove)) {
6492             DisplayMoveError(_("It is Black's turn"));
6493             return FALSE;
6494         }
6495         break;
6496
6497       default:
6498       case IcsExamining:
6499         break;
6500     }
6501     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6502         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6503         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6504         && gameMode != AnalyzeFile && gameMode != Training) {
6505         DisplayMoveError(_("Displayed position is not current"));
6506         return FALSE;
6507     }
6508     return TRUE;
6509 }
6510
6511 Boolean
6512 OnlyMove (int *x, int *y, Boolean captures) 
6513 {
6514     DisambiguateClosure cl;
6515     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6516     switch(gameMode) {
6517       case MachinePlaysBlack:
6518       case IcsPlayingWhite:
6519       case BeginningOfGame:
6520         if(!WhiteOnMove(currentMove)) return FALSE;
6521         break;
6522       case MachinePlaysWhite:
6523       case IcsPlayingBlack:
6524         if(WhiteOnMove(currentMove)) return FALSE;
6525         break;
6526       case EditGame:
6527         break;
6528       default:
6529         return FALSE;
6530     }
6531     cl.pieceIn = EmptySquare;
6532     cl.rfIn = *y;
6533     cl.ffIn = *x;
6534     cl.rtIn = -1;
6535     cl.ftIn = -1;
6536     cl.promoCharIn = NULLCHAR;
6537     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6538     if( cl.kind == NormalMove ||
6539         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6540         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6541         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6542       fromX = cl.ff;
6543       fromY = cl.rf;
6544       *x = cl.ft;
6545       *y = cl.rt;
6546       return TRUE;
6547     }
6548     if(cl.kind != ImpossibleMove) return FALSE;
6549     cl.pieceIn = EmptySquare;
6550     cl.rfIn = -1;
6551     cl.ffIn = -1;
6552     cl.rtIn = *y;
6553     cl.ftIn = *x;
6554     cl.promoCharIn = NULLCHAR;
6555     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6556     if( cl.kind == NormalMove ||
6557         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6558         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6559         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6560       fromX = cl.ff;
6561       fromY = cl.rf;
6562       *x = cl.ft;
6563       *y = cl.rt;
6564       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6565       return TRUE;
6566     }
6567     return FALSE;
6568 }
6569
6570 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6571 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6572 int lastLoadGameUseList = FALSE;
6573 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6574 ChessMove lastLoadGameStart = EndOfFile;
6575 int doubleClick;
6576
6577 void
6578 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6579 {
6580     ChessMove moveType;
6581     ChessSquare pup;
6582     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6583
6584     /* Check if the user is playing in turn.  This is complicated because we
6585        let the user "pick up" a piece before it is his turn.  So the piece he
6586        tried to pick up may have been captured by the time he puts it down!
6587        Therefore we use the color the user is supposed to be playing in this
6588        test, not the color of the piece that is currently on the starting
6589        square---except in EditGame mode, where the user is playing both
6590        sides; fortunately there the capture race can't happen.  (It can
6591        now happen in IcsExamining mode, but that's just too bad.  The user
6592        will get a somewhat confusing message in that case.)
6593        */
6594
6595     switch (gameMode) {
6596       case AnalyzeFile:
6597       case TwoMachinesPlay:
6598       case EndOfGame:
6599       case IcsObserving:
6600       case IcsIdle:
6601         /* We switched into a game mode where moves are not accepted,
6602            perhaps while the mouse button was down. */
6603         return;
6604
6605       case MachinePlaysWhite:
6606         /* User is moving for Black */
6607         if (WhiteOnMove(currentMove)) {
6608             DisplayMoveError(_("It is White's turn"));
6609             return;
6610         }
6611         break;
6612
6613       case MachinePlaysBlack:
6614         /* User is moving for White */
6615         if (!WhiteOnMove(currentMove)) {
6616             DisplayMoveError(_("It is Black's turn"));
6617             return;
6618         }
6619         break;
6620
6621       case PlayFromGameFile:
6622             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6623       case EditGame:
6624       case IcsExamining:
6625       case BeginningOfGame:
6626       case AnalyzeMode:
6627       case Training:
6628         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6629         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6630             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6631             /* User is moving for Black */
6632             if (WhiteOnMove(currentMove)) {
6633                 DisplayMoveError(_("It is White's turn"));
6634                 return;
6635             }
6636         } else {
6637             /* User is moving for White */
6638             if (!WhiteOnMove(currentMove)) {
6639                 DisplayMoveError(_("It is Black's turn"));
6640                 return;
6641             }
6642         }
6643         break;
6644
6645       case IcsPlayingBlack:
6646         /* User is moving for Black */
6647         if (WhiteOnMove(currentMove)) {
6648             if (!appData.premove) {
6649                 DisplayMoveError(_("It is White's turn"));
6650             } else if (toX >= 0 && toY >= 0) {
6651                 premoveToX = toX;
6652                 premoveToY = toY;
6653                 premoveFromX = fromX;
6654                 premoveFromY = fromY;
6655                 premovePromoChar = promoChar;
6656                 gotPremove = 1;
6657                 if (appData.debugMode)
6658                     fprintf(debugFP, "Got premove: fromX %d,"
6659                             "fromY %d, toX %d, toY %d\n",
6660                             fromX, fromY, toX, toY);
6661             }
6662             return;
6663         }
6664         break;
6665
6666       case IcsPlayingWhite:
6667         /* User is moving for White */
6668         if (!WhiteOnMove(currentMove)) {
6669             if (!appData.premove) {
6670                 DisplayMoveError(_("It is Black's turn"));
6671             } else if (toX >= 0 && toY >= 0) {
6672                 premoveToX = toX;
6673                 premoveToY = toY;
6674                 premoveFromX = fromX;
6675                 premoveFromY = fromY;
6676                 premovePromoChar = promoChar;
6677                 gotPremove = 1;
6678                 if (appData.debugMode)
6679                     fprintf(debugFP, "Got premove: fromX %d,"
6680                             "fromY %d, toX %d, toY %d\n",
6681                             fromX, fromY, toX, toY);
6682             }
6683             return;
6684         }
6685         break;
6686
6687       default:
6688         break;
6689
6690       case EditPosition:
6691         /* EditPosition, empty square, or different color piece;
6692            click-click move is possible */
6693         if (toX == -2 || toY == -2) {
6694             boards[0][fromY][fromX] = EmptySquare;
6695             DrawPosition(FALSE, boards[currentMove]);
6696             return;
6697         } else if (toX >= 0 && toY >= 0) {
6698             boards[0][toY][toX] = boards[0][fromY][fromX];
6699             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6700                 if(boards[0][fromY][0] != EmptySquare) {
6701                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6702                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6703                 }
6704             } else
6705             if(fromX == BOARD_RGHT+1) {
6706                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6707                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6708                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6709                 }
6710             } else
6711             boards[0][fromY][fromX] = gatingPiece;
6712             DrawPosition(FALSE, boards[currentMove]);
6713             return;
6714         }
6715         return;
6716     }
6717
6718     if(toX < 0 || toY < 0) return;
6719     pup = boards[currentMove][toY][toX];
6720
6721     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6722     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6723          if( pup != EmptySquare ) return;
6724          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6725            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6726                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6727            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6728            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6729            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6730            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6731          fromY = DROP_RANK;
6732     }
6733
6734     /* [HGM] always test for legality, to get promotion info */
6735     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6736                                          fromY, fromX, toY, toX, promoChar);
6737
6738     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6739
6740     /* [HGM] but possibly ignore an IllegalMove result */
6741     if (appData.testLegality) {
6742         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6743             DisplayMoveError(_("Illegal move"));
6744             return;
6745         }
6746     }
6747
6748     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6749         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6750              ClearPremoveHighlights(); // was included
6751         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6752         return;
6753     }
6754
6755     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6756 }
6757
6758 /* Common tail of UserMoveEvent and DropMenuEvent */
6759 int
6760 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6761 {
6762     char *bookHit = 0;
6763
6764     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6765         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6766         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6767         if(WhiteOnMove(currentMove)) {
6768             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6769         } else {
6770             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6771         }
6772     }
6773
6774     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6775        move type in caller when we know the move is a legal promotion */
6776     if(moveType == NormalMove && promoChar)
6777         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6778
6779     /* [HGM] <popupFix> The following if has been moved here from
6780        UserMoveEvent(). Because it seemed to belong here (why not allow
6781        piece drops in training games?), and because it can only be
6782        performed after it is known to what we promote. */
6783     if (gameMode == Training) {
6784       /* compare the move played on the board to the next move in the
6785        * game. If they match, display the move and the opponent's response.
6786        * If they don't match, display an error message.
6787        */
6788       int saveAnimate;
6789       Board testBoard;
6790       CopyBoard(testBoard, boards[currentMove]);
6791       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6792
6793       if (CompareBoards(testBoard, boards[currentMove+1])) {
6794         ForwardInner(currentMove+1);
6795
6796         /* Autoplay the opponent's response.
6797          * if appData.animate was TRUE when Training mode was entered,
6798          * the response will be animated.
6799          */
6800         saveAnimate = appData.animate;
6801         appData.animate = animateTraining;
6802         ForwardInner(currentMove+1);
6803         appData.animate = saveAnimate;
6804
6805         /* check for the end of the game */
6806         if (currentMove >= forwardMostMove) {
6807           gameMode = PlayFromGameFile;
6808           ModeHighlight();
6809           SetTrainingModeOff();
6810           DisplayInformation(_("End of game"));
6811         }
6812       } else {
6813         DisplayError(_("Incorrect move"), 0);
6814       }
6815       return 1;
6816     }
6817
6818   /* Ok, now we know that the move is good, so we can kill
6819      the previous line in Analysis Mode */
6820   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6821                                 && currentMove < forwardMostMove) {
6822     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6823     else forwardMostMove = currentMove;
6824   }
6825
6826   ClearMap();
6827
6828   /* If we need the chess program but it's dead, restart it */
6829   ResurrectChessProgram();
6830
6831   /* A user move restarts a paused game*/
6832   if (pausing)
6833     PauseEvent();
6834
6835   thinkOutput[0] = NULLCHAR;
6836
6837   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6838
6839   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6840     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6841     return 1;
6842   }
6843
6844   if (gameMode == BeginningOfGame) {
6845     if (appData.noChessProgram) {
6846       gameMode = EditGame;
6847       SetGameInfo();
6848     } else {
6849       char buf[MSG_SIZ];
6850       gameMode = MachinePlaysBlack;
6851       StartClocks();
6852       SetGameInfo();
6853       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6854       DisplayTitle(buf);
6855       if (first.sendName) {
6856         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6857         SendToProgram(buf, &first);
6858       }
6859       StartClocks();
6860     }
6861     ModeHighlight();
6862   }
6863
6864   /* Relay move to ICS or chess engine */
6865   if (appData.icsActive) {
6866     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6867         gameMode == IcsExamining) {
6868       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6869         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6870         SendToICS("draw ");
6871         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6872       }
6873       // also send plain move, in case ICS does not understand atomic claims
6874       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6875       ics_user_moved = 1;
6876     }
6877   } else {
6878     if (first.sendTime && (gameMode == BeginningOfGame ||
6879                            gameMode == MachinePlaysWhite ||
6880                            gameMode == MachinePlaysBlack)) {
6881       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6882     }
6883     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6884          // [HGM] book: if program might be playing, let it use book
6885         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6886         first.maybeThinking = TRUE;
6887     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6888         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6889         SendBoard(&first, currentMove+1);
6890         if(second.analyzing) {
6891             if(!second.useSetboard) SendToProgram("undo\n", &second);
6892             SendBoard(&second, currentMove+1);
6893         }
6894     } else {
6895         SendMoveToProgram(forwardMostMove-1, &first);
6896         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6897     }
6898     if (currentMove == cmailOldMove + 1) {
6899       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6900     }
6901   }
6902
6903   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6904
6905   switch (gameMode) {
6906   case EditGame:
6907     if(appData.testLegality)
6908     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6909     case MT_NONE:
6910     case MT_CHECK:
6911       break;
6912     case MT_CHECKMATE:
6913     case MT_STAINMATE:
6914       if (WhiteOnMove(currentMove)) {
6915         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6916       } else {
6917         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6918       }
6919       break;
6920     case MT_STALEMATE:
6921       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6922       break;
6923     }
6924     break;
6925
6926   case MachinePlaysBlack:
6927   case MachinePlaysWhite:
6928     /* disable certain menu options while machine is thinking */
6929     SetMachineThinkingEnables();
6930     break;
6931
6932   default:
6933     break;
6934   }
6935
6936   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6937   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6938
6939   if(bookHit) { // [HGM] book: simulate book reply
6940         static char bookMove[MSG_SIZ]; // a bit generous?
6941
6942         programStats.nodes = programStats.depth = programStats.time =
6943         programStats.score = programStats.got_only_move = 0;
6944         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6945
6946         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6947         strcat(bookMove, bookHit);
6948         HandleMachineMove(bookMove, &first);
6949   }
6950   return 1;
6951 }
6952
6953 void
6954 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6955 {
6956     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6957     Markers *m = (Markers *) closure;
6958     if(rf == fromY && ff == fromX)
6959         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6960                          || kind == WhiteCapturesEnPassant
6961                          || kind == BlackCapturesEnPassant);
6962     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6963 }
6964
6965 void
6966 MarkTargetSquares (int clear)
6967 {
6968   int x, y;
6969   if(clear) // no reason to ever suppress clearing
6970     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6971   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6972      !appData.testLegality || gameMode == EditPosition) return;
6973   if(!clear) {
6974     int capt = 0;
6975     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6976     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6977       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6978       if(capt)
6979       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6980     }
6981   }
6982   DrawPosition(FALSE, NULL);
6983 }
6984
6985 int
6986 Explode (Board board, int fromX, int fromY, int toX, int toY)
6987 {
6988     if(gameInfo.variant == VariantAtomic &&
6989        (board[toY][toX] != EmptySquare ||                     // capture?
6990         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6991                          board[fromY][fromX] == BlackPawn   )
6992       )) {
6993         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6994         return TRUE;
6995     }
6996     return FALSE;
6997 }
6998
6999 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7000
7001 int
7002 CanPromote (ChessSquare piece, int y)
7003 {
7004         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7005         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7006         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7007            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7008            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7009                                                   gameInfo.variant == VariantMakruk) return FALSE;
7010         return (piece == BlackPawn && y == 1 ||
7011                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7012                 piece == BlackLance && y == 1 ||
7013                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7014 }
7015
7016 void
7017 LeftClick (ClickType clickType, int xPix, int yPix)
7018 {
7019     int x, y;
7020     Boolean saveAnimate;
7021     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7022     char promoChoice = NULLCHAR;
7023     ChessSquare piece;
7024     static TimeMark lastClickTime, prevClickTime;
7025
7026     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7027
7028     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7029
7030     if (clickType == Press) ErrorPopDown();
7031
7032     x = EventToSquare(xPix, BOARD_WIDTH);
7033     y = EventToSquare(yPix, BOARD_HEIGHT);
7034     if (!flipView && y >= 0) {
7035         y = BOARD_HEIGHT - 1 - y;
7036     }
7037     if (flipView && x >= 0) {
7038         x = BOARD_WIDTH - 1 - x;
7039     }
7040
7041     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7042         defaultPromoChoice = promoSweep;
7043         promoSweep = EmptySquare;   // terminate sweep
7044         promoDefaultAltered = TRUE;
7045         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7046     }
7047
7048     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7049         if(clickType == Release) return; // ignore upclick of click-click destination
7050         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7051         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7052         if(gameInfo.holdingsWidth &&
7053                 (WhiteOnMove(currentMove)
7054                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7055                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7056             // click in right holdings, for determining promotion piece
7057             ChessSquare p = boards[currentMove][y][x];
7058             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7059             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7060             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7061                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7062                 fromX = fromY = -1;
7063                 return;
7064             }
7065         }
7066         DrawPosition(FALSE, boards[currentMove]);
7067         return;
7068     }
7069
7070     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7071     if(clickType == Press
7072             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7073               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7074               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7075         return;
7076
7077     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7078         // could be static click on premove from-square: abort premove
7079         gotPremove = 0;
7080         ClearPremoveHighlights();
7081     }
7082
7083     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7084         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7085
7086     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7087         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7088                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7089         defaultPromoChoice = DefaultPromoChoice(side);
7090     }
7091
7092     autoQueen = appData.alwaysPromoteToQueen;
7093
7094     if (fromX == -1) {
7095       int originalY = y;
7096       gatingPiece = EmptySquare;
7097       if (clickType != Press) {
7098         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7099             DragPieceEnd(xPix, yPix); dragging = 0;
7100             DrawPosition(FALSE, NULL);
7101         }
7102         return;
7103       }
7104       doubleClick = FALSE;
7105       if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves
7106         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7107       }
7108       fromX = x; fromY = y; toX = toY = -1;
7109       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7110          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7111          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7112             /* First square */
7113             if (OKToStartUserMove(fromX, fromY)) {
7114                 second = 0;
7115                 MarkTargetSquares(0);
7116                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7117                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7118                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7119                     promoSweep = defaultPromoChoice;
7120                     selectFlag = 0; lastX = xPix; lastY = yPix;
7121                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7122                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7123                 }
7124                 if (appData.highlightDragging) {
7125                     SetHighlights(fromX, fromY, -1, -1);
7126                 } else {
7127                     ClearHighlights();
7128                 }
7129             } else fromX = fromY = -1;
7130             return;
7131         }
7132     }
7133
7134     /* fromX != -1 */
7135     if (clickType == Press && gameMode != EditPosition) {
7136         ChessSquare fromP;
7137         ChessSquare toP;
7138         int frc;
7139
7140         // ignore off-board to clicks
7141         if(y < 0 || x < 0) return;
7142
7143         /* Check if clicking again on the same color piece */
7144         fromP = boards[currentMove][fromY][fromX];
7145         toP = boards[currentMove][y][x];
7146         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7147         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7148              WhitePawn <= toP && toP <= WhiteKing &&
7149              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7150              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7151             (BlackPawn <= fromP && fromP <= BlackKing &&
7152              BlackPawn <= toP && toP <= BlackKing &&
7153              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7154              !(fromP == BlackKing && toP == BlackRook && frc))) {
7155             /* Clicked again on same color piece -- changed his mind */
7156             second = (x == fromX && y == fromY);
7157             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7158                 second = FALSE; // first double-click rather than scond click
7159                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7160             }
7161             promoDefaultAltered = FALSE;
7162             MarkTargetSquares(1);
7163            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7164             if (appData.highlightDragging) {
7165                 SetHighlights(x, y, -1, -1);
7166             } else {
7167                 ClearHighlights();
7168             }
7169             if (OKToStartUserMove(x, y)) {
7170                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7171                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7172                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7173                  gatingPiece = boards[currentMove][fromY][fromX];
7174                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7175                 fromX = x;
7176                 fromY = y; dragging = 1;
7177                 MarkTargetSquares(0);
7178                 DragPieceBegin(xPix, yPix, FALSE);
7179                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7180                     promoSweep = defaultPromoChoice;
7181                     selectFlag = 0; lastX = xPix; lastY = yPix;
7182                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7183                 }
7184             }
7185            }
7186            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7187            second = FALSE; 
7188         }
7189         // ignore clicks on holdings
7190         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7191     }
7192
7193     if (clickType == Release && x == fromX && y == fromY) {
7194         DragPieceEnd(xPix, yPix); dragging = 0;
7195         if(clearFlag) {
7196             // a deferred attempt to click-click move an empty square on top of a piece
7197             boards[currentMove][y][x] = EmptySquare;
7198             ClearHighlights();
7199             DrawPosition(FALSE, boards[currentMove]);
7200             fromX = fromY = -1; clearFlag = 0;
7201             return;
7202         }
7203         if (appData.animateDragging) {
7204             /* Undo animation damage if any */
7205             DrawPosition(FALSE, NULL);
7206         }
7207         if (second || sweepSelecting) {
7208             /* Second up/down in same square; just abort move */
7209             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7210             second = sweepSelecting = 0;
7211             fromX = fromY = -1;
7212             gatingPiece = EmptySquare;
7213             ClearHighlights();
7214             gotPremove = 0;
7215             ClearPremoveHighlights();
7216         } else {
7217             /* First upclick in same square; start click-click mode */
7218             SetHighlights(x, y, -1, -1);
7219         }
7220         return;
7221     }
7222
7223     clearFlag = 0;
7224
7225     /* we now have a different from- and (possibly off-board) to-square */
7226     /* Completed move */
7227     if(!sweepSelecting) {
7228         toX = x;
7229         toY = y;
7230     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7231
7232     saveAnimate = appData.animate;
7233     if (clickType == Press) {
7234         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7235             // must be Edit Position mode with empty-square selected
7236             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7237             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7238             return;
7239         }
7240         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7241           if(appData.sweepSelect) {
7242             ChessSquare piece = boards[currentMove][fromY][fromX];
7243             promoSweep = defaultPromoChoice;
7244             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7245             selectFlag = 0; lastX = xPix; lastY = yPix;
7246             Sweep(0); // Pawn that is going to promote: preview promotion piece
7247             sweepSelecting = 1;
7248             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7249             MarkTargetSquares(1);
7250           }
7251           return; // promo popup appears on up-click
7252         }
7253         /* Finish clickclick move */
7254         if (appData.animate || appData.highlightLastMove) {
7255             SetHighlights(fromX, fromY, toX, toY);
7256         } else {
7257             ClearHighlights();
7258         }
7259     } else {
7260         /* Finish drag move */
7261         if (appData.highlightLastMove) {
7262             SetHighlights(fromX, fromY, toX, toY);
7263         } else {
7264             ClearHighlights();
7265         }
7266         DragPieceEnd(xPix, yPix); dragging = 0;
7267         /* Don't animate move and drag both */
7268         appData.animate = FALSE;
7269     }
7270     MarkTargetSquares(1);
7271
7272     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7273     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7274         ChessSquare piece = boards[currentMove][fromY][fromX];
7275         if(gameMode == EditPosition && piece != EmptySquare &&
7276            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7277             int n;
7278
7279             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7280                 n = PieceToNumber(piece - (int)BlackPawn);
7281                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7282                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7283                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7284             } else
7285             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7286                 n = PieceToNumber(piece);
7287                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7288                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7289                 boards[currentMove][n][BOARD_WIDTH-2]++;
7290             }
7291             boards[currentMove][fromY][fromX] = EmptySquare;
7292         }
7293         ClearHighlights();
7294         fromX = fromY = -1;
7295         DrawPosition(TRUE, boards[currentMove]);
7296         return;
7297     }
7298
7299     // off-board moves should not be highlighted
7300     if(x < 0 || y < 0) ClearHighlights();
7301
7302     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7303
7304     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7305         SetHighlights(fromX, fromY, toX, toY);
7306         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7307             // [HGM] super: promotion to captured piece selected from holdings
7308             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7309             promotionChoice = TRUE;
7310             // kludge follows to temporarily execute move on display, without promoting yet
7311             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7312             boards[currentMove][toY][toX] = p;
7313             DrawPosition(FALSE, boards[currentMove]);
7314             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7315             boards[currentMove][toY][toX] = q;
7316             DisplayMessage("Click in holdings to choose piece", "");
7317             return;
7318         }
7319         PromotionPopUp();
7320     } else {
7321         int oldMove = currentMove;
7322         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7323         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7324         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7325         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7326            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7327             DrawPosition(TRUE, boards[currentMove]);
7328         fromX = fromY = -1;
7329     }
7330     appData.animate = saveAnimate;
7331     if (appData.animate || appData.animateDragging) {
7332         /* Undo animation damage if needed */
7333         DrawPosition(FALSE, NULL);
7334     }
7335 }
7336
7337 int
7338 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7339 {   // front-end-free part taken out of PieceMenuPopup
7340     int whichMenu; int xSqr, ySqr;
7341
7342     if(seekGraphUp) { // [HGM] seekgraph
7343         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7344         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7345         return -2;
7346     }
7347
7348     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7349          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7350         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7351         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7352         if(action == Press)   {
7353             originalFlip = flipView;
7354             flipView = !flipView; // temporarily flip board to see game from partners perspective
7355             DrawPosition(TRUE, partnerBoard);
7356             DisplayMessage(partnerStatus, "");
7357             partnerUp = TRUE;
7358         } else if(action == Release) {
7359             flipView = originalFlip;
7360             DrawPosition(TRUE, boards[currentMove]);
7361             partnerUp = FALSE;
7362         }
7363         return -2;
7364     }
7365
7366     xSqr = EventToSquare(x, BOARD_WIDTH);
7367     ySqr = EventToSquare(y, BOARD_HEIGHT);
7368     if (action == Release) {
7369         if(pieceSweep != EmptySquare) {
7370             EditPositionMenuEvent(pieceSweep, toX, toY);
7371             pieceSweep = EmptySquare;
7372         } else UnLoadPV(); // [HGM] pv
7373     }
7374     if (action != Press) return -2; // return code to be ignored
7375     switch (gameMode) {
7376       case IcsExamining:
7377         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7378       case EditPosition:
7379         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7380         if (xSqr < 0 || ySqr < 0) return -1;
7381         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7382         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7383         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7384         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7385         NextPiece(0);
7386         return 2; // grab
7387       case IcsObserving:
7388         if(!appData.icsEngineAnalyze) return -1;
7389       case IcsPlayingWhite:
7390       case IcsPlayingBlack:
7391         if(!appData.zippyPlay) goto noZip;
7392       case AnalyzeMode:
7393       case AnalyzeFile:
7394       case MachinePlaysWhite:
7395       case MachinePlaysBlack:
7396       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7397         if (!appData.dropMenu) {
7398           LoadPV(x, y);
7399           return 2; // flag front-end to grab mouse events
7400         }
7401         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7402            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7403       case EditGame:
7404       noZip:
7405         if (xSqr < 0 || ySqr < 0) return -1;
7406         if (!appData.dropMenu || appData.testLegality &&
7407             gameInfo.variant != VariantBughouse &&
7408             gameInfo.variant != VariantCrazyhouse) return -1;
7409         whichMenu = 1; // drop menu
7410         break;
7411       default:
7412         return -1;
7413     }
7414
7415     if (((*fromX = xSqr) < 0) ||
7416         ((*fromY = ySqr) < 0)) {
7417         *fromX = *fromY = -1;
7418         return -1;
7419     }
7420     if (flipView)
7421       *fromX = BOARD_WIDTH - 1 - *fromX;
7422     else
7423       *fromY = BOARD_HEIGHT - 1 - *fromY;
7424
7425     return whichMenu;
7426 }
7427
7428 void
7429 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7430 {
7431 //    char * hint = lastHint;
7432     FrontEndProgramStats stats;
7433
7434     stats.which = cps == &first ? 0 : 1;
7435     stats.depth = cpstats->depth;
7436     stats.nodes = cpstats->nodes;
7437     stats.score = cpstats->score;
7438     stats.time = cpstats->time;
7439     stats.pv = cpstats->movelist;
7440     stats.hint = lastHint;
7441     stats.an_move_index = 0;
7442     stats.an_move_count = 0;
7443
7444     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7445         stats.hint = cpstats->move_name;
7446         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7447         stats.an_move_count = cpstats->nr_moves;
7448     }
7449
7450     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
7451
7452     SetProgramStats( &stats );
7453 }
7454
7455 void
7456 ClearEngineOutputPane (int which)
7457 {
7458     static FrontEndProgramStats dummyStats;
7459     dummyStats.which = which;
7460     dummyStats.pv = "#";
7461     SetProgramStats( &dummyStats );
7462 }
7463
7464 #define MAXPLAYERS 500
7465
7466 char *
7467 TourneyStandings (int display)
7468 {
7469     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7470     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7471     char result, *p, *names[MAXPLAYERS];
7472
7473     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7474         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7475     names[0] = p = strdup(appData.participants);
7476     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7477
7478     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7479
7480     while(result = appData.results[nr]) {
7481         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7482         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7483         wScore = bScore = 0;
7484         switch(result) {
7485           case '+': wScore = 2; break;
7486           case '-': bScore = 2; break;
7487           case '=': wScore = bScore = 1; break;
7488           case ' ':
7489           case '*': return strdup("busy"); // tourney not finished
7490         }
7491         score[w] += wScore;
7492         score[b] += bScore;
7493         games[w]++;
7494         games[b]++;
7495         nr++;
7496     }
7497     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7498     for(w=0; w<nPlayers; w++) {
7499         bScore = -1;
7500         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7501         ranking[w] = b; points[w] = bScore; score[b] = -2;
7502     }
7503     p = malloc(nPlayers*34+1);
7504     for(w=0; w<nPlayers && w<display; w++)
7505         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7506     free(names[0]);
7507     return p;
7508 }
7509
7510 void
7511 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7512 {       // count all piece types
7513         int p, f, r;
7514         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7515         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7516         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7517                 p = board[r][f];
7518                 pCnt[p]++;
7519                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7520                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7521                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7522                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7523                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7524                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7525         }
7526 }
7527
7528 int
7529 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7530 {
7531         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7532         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7533
7534         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7535         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7536         if(myPawns == 2 && nMine == 3) // KPP
7537             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7538         if(myPawns == 1 && nMine == 2) // KP
7539             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7540         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7541             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7542         if(myPawns) return FALSE;
7543         if(pCnt[WhiteRook+side])
7544             return pCnt[BlackRook-side] ||
7545                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7546                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7547                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7548         if(pCnt[WhiteCannon+side]) {
7549             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7550             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7551         }
7552         if(pCnt[WhiteKnight+side])
7553             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7554         return FALSE;
7555 }
7556
7557 int
7558 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7559 {
7560         VariantClass v = gameInfo.variant;
7561
7562         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7563         if(v == VariantShatranj) return TRUE; // always winnable through baring
7564         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7565         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7566
7567         if(v == VariantXiangqi) {
7568                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7569
7570                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7571                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7572                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7573                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7574                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7575                 if(stale) // we have at least one last-rank P plus perhaps C
7576                     return majors // KPKX
7577                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7578                 else // KCA*E*
7579                     return pCnt[WhiteFerz+side] // KCAK
7580                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7581                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7582                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7583
7584         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7585                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7586
7587                 if(nMine == 1) return FALSE; // bare King
7588                 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
7589                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7590                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7591                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7592                 if(pCnt[WhiteKnight+side])
7593                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7594                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7595                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7596                 if(nBishops)
7597                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7598                 if(pCnt[WhiteAlfil+side])
7599                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7600                 if(pCnt[WhiteWazir+side])
7601                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7602         }
7603
7604         return TRUE;
7605 }
7606
7607 int
7608 CompareWithRights (Board b1, Board b2)
7609 {
7610     int rights = 0;
7611     if(!CompareBoards(b1, b2)) return FALSE;
7612     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7613     /* compare castling rights */
7614     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7615            rights++; /* King lost rights, while rook still had them */
7616     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7617         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7618            rights++; /* but at least one rook lost them */
7619     }
7620     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7621            rights++;
7622     if( b1[CASTLING][5] != NoRights ) {
7623         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7624            rights++;
7625     }
7626     return rights == 0;
7627 }
7628
7629 int
7630 Adjudicate (ChessProgramState *cps)
7631 {       // [HGM] some adjudications useful with buggy engines
7632         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7633         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7634         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7635         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7636         int k, count = 0; static int bare = 1;
7637         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7638         Boolean canAdjudicate = !appData.icsActive;
7639
7640         // most tests only when we understand the game, i.e. legality-checking on
7641             if( appData.testLegality )
7642             {   /* [HGM] Some more adjudications for obstinate engines */
7643                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7644                 static int moveCount = 6;
7645                 ChessMove result;
7646                 char *reason = NULL;
7647
7648                 /* Count what is on board. */
7649                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7650
7651                 /* Some material-based adjudications that have to be made before stalemate test */
7652                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7653                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7654                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7655                      if(canAdjudicate && appData.checkMates) {
7656                          if(engineOpponent)
7657                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7658                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7659                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7660                          return 1;
7661                      }
7662                 }
7663
7664                 /* Bare King in Shatranj (loses) or Losers (wins) */
7665                 if( nrW == 1 || nrB == 1) {
7666                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7667                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7668                      if(canAdjudicate && appData.checkMates) {
7669                          if(engineOpponent)
7670                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7671                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7672                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7673                          return 1;
7674                      }
7675                   } else
7676                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7677                   {    /* bare King */
7678                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7679                         if(canAdjudicate && appData.checkMates) {
7680                             /* but only adjudicate if adjudication enabled */
7681                             if(engineOpponent)
7682                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7683                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7684                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7685                             return 1;
7686                         }
7687                   }
7688                 } else bare = 1;
7689
7690
7691             // don't wait for engine to announce game end if we can judge ourselves
7692             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7693               case MT_CHECK:
7694                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7695                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7696                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7697                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7698                             checkCnt++;
7699                         if(checkCnt >= 2) {
7700                             reason = "Xboard adjudication: 3rd check";
7701                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7702                             break;
7703                         }
7704                     }
7705                 }
7706               case MT_NONE:
7707               default:
7708                 break;
7709               case MT_STALEMATE:
7710               case MT_STAINMATE:
7711                 reason = "Xboard adjudication: Stalemate";
7712                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7713                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7714                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7715                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7716                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7717                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7718                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7719                                                                         EP_CHECKMATE : EP_WINS);
7720                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7721                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7722                 }
7723                 break;
7724               case MT_CHECKMATE:
7725                 reason = "Xboard adjudication: Checkmate";
7726                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7727                 break;
7728             }
7729
7730                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7731                     case EP_STALEMATE:
7732                         result = GameIsDrawn; break;
7733                     case EP_CHECKMATE:
7734                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7735                     case EP_WINS:
7736                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7737                     default:
7738                         result = EndOfFile;
7739                 }
7740                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7741                     if(engineOpponent)
7742                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7743                     GameEnds( result, reason, GE_XBOARD );
7744                     return 1;
7745                 }
7746
7747                 /* Next absolutely insufficient mating material. */
7748                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7749                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7750                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7751
7752                      /* always flag draws, for judging claims */
7753                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7754
7755                      if(canAdjudicate && appData.materialDraws) {
7756                          /* but only adjudicate them if adjudication enabled */
7757                          if(engineOpponent) {
7758                            SendToProgram("force\n", engineOpponent); // suppress reply
7759                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7760                          }
7761                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7762                          return 1;
7763                      }
7764                 }
7765
7766                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7767                 if(gameInfo.variant == VariantXiangqi ?
7768                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7769                  : nrW + nrB == 4 &&
7770                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7771                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7772                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7773                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7774                    ) ) {
7775                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7776                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7777                           if(engineOpponent) {
7778                             SendToProgram("force\n", engineOpponent); // suppress reply
7779                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7780                           }
7781                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7782                           return 1;
7783                      }
7784                 } else moveCount = 6;
7785             }
7786
7787         // Repetition draws and 50-move rule can be applied independently of legality testing
7788
7789                 /* Check for rep-draws */
7790                 count = 0;
7791                 for(k = forwardMostMove-2;
7792                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7793                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7794                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7795                     k-=2)
7796                 {   int rights=0;
7797                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7798                         /* compare castling rights */
7799                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7800                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7801                                 rights++; /* King lost rights, while rook still had them */
7802                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7803                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7804                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7805                                    rights++; /* but at least one rook lost them */
7806                         }
7807                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7808                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7809                                 rights++;
7810                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7811                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7812                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7813                                    rights++;
7814                         }
7815                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7816                             && appData.drawRepeats > 1) {
7817                              /* adjudicate after user-specified nr of repeats */
7818                              int result = GameIsDrawn;
7819                              char *details = "XBoard adjudication: repetition draw";
7820                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7821                                 // [HGM] xiangqi: check for forbidden perpetuals
7822                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7823                                 for(m=forwardMostMove; m>k; m-=2) {
7824                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7825                                         ourPerpetual = 0; // the current mover did not always check
7826                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7827                                         hisPerpetual = 0; // the opponent did not always check
7828                                 }
7829                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7830                                                                         ourPerpetual, hisPerpetual);
7831                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7832                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7833                                     details = "Xboard adjudication: perpetual checking";
7834                                 } else
7835                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7836                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7837                                 } else
7838                                 // Now check for perpetual chases
7839                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7840                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7841                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7842                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7843                                         static char resdet[MSG_SIZ];
7844                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7845                                         details = resdet;
7846                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7847                                     } else
7848                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7849                                         break; // Abort repetition-checking loop.
7850                                 }
7851                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7852                              }
7853                              if(engineOpponent) {
7854                                SendToProgram("force\n", engineOpponent); // suppress reply
7855                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7856                              }
7857                              GameEnds( result, details, GE_XBOARD );
7858                              return 1;
7859                         }
7860                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7861                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7862                     }
7863                 }
7864
7865                 /* Now we test for 50-move draws. Determine ply count */
7866                 count = forwardMostMove;
7867                 /* look for last irreversble move */
7868                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7869                     count--;
7870                 /* if we hit starting position, add initial plies */
7871                 if( count == backwardMostMove )
7872                     count -= initialRulePlies;
7873                 count = forwardMostMove - count;
7874                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7875                         // adjust reversible move counter for checks in Xiangqi
7876                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7877                         if(i < backwardMostMove) i = backwardMostMove;
7878                         while(i <= forwardMostMove) {
7879                                 lastCheck = inCheck; // check evasion does not count
7880                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7881                                 if(inCheck || lastCheck) count--; // check does not count
7882                                 i++;
7883                         }
7884                 }
7885                 if( count >= 100)
7886                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7887                          /* this is used to judge if draw claims are legal */
7888                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7889                          if(engineOpponent) {
7890                            SendToProgram("force\n", engineOpponent); // suppress reply
7891                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7892                          }
7893                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7894                          return 1;
7895                 }
7896
7897                 /* if draw offer is pending, treat it as a draw claim
7898                  * when draw condition present, to allow engines a way to
7899                  * claim draws before making their move to avoid a race
7900                  * condition occurring after their move
7901                  */
7902                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7903                          char *p = NULL;
7904                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7905                              p = "Draw claim: 50-move rule";
7906                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7907                              p = "Draw claim: 3-fold repetition";
7908                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7909                              p = "Draw claim: insufficient mating material";
7910                          if( p != NULL && canAdjudicate) {
7911                              if(engineOpponent) {
7912                                SendToProgram("force\n", engineOpponent); // suppress reply
7913                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7914                              }
7915                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7916                              return 1;
7917                          }
7918                 }
7919
7920                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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, "Xboard adjudication: long game", GE_XBOARD );
7926                     return 1;
7927                 }
7928         return 0;
7929 }
7930
7931 char *
7932 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7933 {   // [HGM] book: this routine intercepts moves to simulate book replies
7934     char *bookHit = NULL;
7935
7936     //first determine if the incoming move brings opponent into his book
7937     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7938         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7939     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7940     if(bookHit != NULL && !cps->bookSuspend) {
7941         // make sure opponent is not going to reply after receiving move to book position
7942         SendToProgram("force\n", cps);
7943         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7944     }
7945     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7946     // now arrange restart after book miss
7947     if(bookHit) {
7948         // after a book hit we never send 'go', and the code after the call to this routine
7949         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7950         char buf[MSG_SIZ], *move = bookHit;
7951         if(cps->useSAN) {
7952             int fromX, fromY, toX, toY;
7953             char promoChar;
7954             ChessMove moveType;
7955             move = buf + 30;
7956             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7957                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7958                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7959                                     PosFlags(forwardMostMove),
7960                                     fromY, fromX, toY, toX, promoChar, move);
7961             } else {
7962                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7963                 bookHit = NULL;
7964             }
7965         }
7966         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7967         SendToProgram(buf, cps);
7968         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7969     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7970         SendToProgram("go\n", cps);
7971         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7972     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7973         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7974             SendToProgram("go\n", cps);
7975         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7976     }
7977     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7978 }
7979
7980 int
7981 LoadError (char *errmess, ChessProgramState *cps)
7982 {   // unloads engine and switches back to -ncp mode if it was first
7983     if(cps->initDone) return FALSE;
7984     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7985     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7986     cps->pr = NoProc; 
7987     if(cps == &first) {
7988         appData.noChessProgram = TRUE;
7989         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7990         gameMode = BeginningOfGame; ModeHighlight();
7991         SetNCPMode();
7992     }
7993     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7994     DisplayMessage("", ""); // erase waiting message
7995     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7996     return TRUE;
7997 }
7998
7999 char *savedMessage;
8000 ChessProgramState *savedState;
8001 void
8002 DeferredBookMove (void)
8003 {
8004         if(savedState->lastPing != savedState->lastPong)
8005                     ScheduleDelayedEvent(DeferredBookMove, 10);
8006         else
8007         HandleMachineMove(savedMessage, savedState);
8008 }
8009
8010 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8011
8012 void
8013 HandleMachineMove (char *message, ChessProgramState *cps)
8014 {
8015     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8016     char realname[MSG_SIZ];
8017     int fromX, fromY, toX, toY;
8018     ChessMove moveType;
8019     char promoChar;
8020     char *p, *pv=buf1;
8021     int machineWhite, oldError;
8022     char *bookHit;
8023
8024     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8025         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8026         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8027             DisplayError(_("Invalid pairing from pairing engine"), 0);
8028             return;
8029         }
8030         pairingReceived = 1;
8031         NextMatchGame();
8032         return; // Skim the pairing messages here.
8033     }
8034
8035     oldError = cps->userError; cps->userError = 0;
8036
8037 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8038     /*
8039      * Kludge to ignore BEL characters
8040      */
8041     while (*message == '\007') message++;
8042
8043     /*
8044      * [HGM] engine debug message: ignore lines starting with '#' character
8045      */
8046     if(cps->debug && *message == '#') return;
8047
8048     /*
8049      * Look for book output
8050      */
8051     if (cps == &first && bookRequested) {
8052         if (message[0] == '\t' || message[0] == ' ') {
8053             /* Part of the book output is here; append it */
8054             strcat(bookOutput, message);
8055             strcat(bookOutput, "  \n");
8056             return;
8057         } else if (bookOutput[0] != NULLCHAR) {
8058             /* All of book output has arrived; display it */
8059             char *p = bookOutput;
8060             while (*p != NULLCHAR) {
8061                 if (*p == '\t') *p = ' ';
8062                 p++;
8063             }
8064             DisplayInformation(bookOutput);
8065             bookRequested = FALSE;
8066             /* Fall through to parse the current output */
8067         }
8068     }
8069
8070     /*
8071      * Look for machine move.
8072      */
8073     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8074         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8075     {
8076         /* This method is only useful on engines that support ping */
8077         if (cps->lastPing != cps->lastPong) {
8078           if (gameMode == BeginningOfGame) {
8079             /* Extra move from before last new; ignore */
8080             if (appData.debugMode) {
8081                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8082             }
8083           } else {
8084             if (appData.debugMode) {
8085                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8086                         cps->which, gameMode);
8087             }
8088
8089             SendToProgram("undo\n", cps);
8090           }
8091           return;
8092         }
8093
8094         switch (gameMode) {
8095           case BeginningOfGame:
8096             /* Extra move from before last reset; ignore */
8097             if (appData.debugMode) {
8098                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8099             }
8100             return;
8101
8102           case EndOfGame:
8103           case IcsIdle:
8104           default:
8105             /* Extra move after we tried to stop.  The mode test is
8106                not a reliable way of detecting this problem, but it's
8107                the best we can do on engines that don't support ping.
8108             */
8109             if (appData.debugMode) {
8110                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8111                         cps->which, gameMode);
8112             }
8113             SendToProgram("undo\n", cps);
8114             return;
8115
8116           case MachinePlaysWhite:
8117           case IcsPlayingWhite:
8118             machineWhite = TRUE;
8119             break;
8120
8121           case MachinePlaysBlack:
8122           case IcsPlayingBlack:
8123             machineWhite = FALSE;
8124             break;
8125
8126           case TwoMachinesPlay:
8127             machineWhite = (cps->twoMachinesColor[0] == 'w');
8128             break;
8129         }
8130         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8131             if (appData.debugMode) {
8132                 fprintf(debugFP,
8133                         "Ignoring move out of turn by %s, gameMode %d"
8134                         ", forwardMost %d\n",
8135                         cps->which, gameMode, forwardMostMove);
8136             }
8137             return;
8138         }
8139
8140         if(cps->alphaRank) AlphaRank(machineMove, 4);
8141         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8142                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8143             /* Machine move could not be parsed; ignore it. */
8144           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8145                     machineMove, _(cps->which));
8146             DisplayError(buf1, 0);
8147             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8148                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8149             if (gameMode == TwoMachinesPlay) {
8150               GameEnds(machineWhite ? BlackWins : WhiteWins,
8151                        buf1, GE_XBOARD);
8152             }
8153             return;
8154         }
8155
8156         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8157         /* So we have to redo legality test with true e.p. status here,  */
8158         /* to make sure an illegal e.p. capture does not slip through,   */
8159         /* to cause a forfeit on a justified illegal-move complaint      */
8160         /* of the opponent.                                              */
8161         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8162            ChessMove moveType;
8163            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8164                              fromY, fromX, toY, toX, promoChar);
8165             if(moveType == IllegalMove) {
8166               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8167                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8168                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8169                            buf1, GE_XBOARD);
8170                 return;
8171            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8172            /* [HGM] Kludge to handle engines that send FRC-style castling
8173               when they shouldn't (like TSCP-Gothic) */
8174            switch(moveType) {
8175              case WhiteASideCastleFR:
8176              case BlackASideCastleFR:
8177                toX+=2;
8178                currentMoveString[2]++;
8179                break;
8180              case WhiteHSideCastleFR:
8181              case BlackHSideCastleFR:
8182                toX--;
8183                currentMoveString[2]--;
8184                break;
8185              default: ; // nothing to do, but suppresses warning of pedantic compilers
8186            }
8187         }
8188         hintRequested = FALSE;
8189         lastHint[0] = NULLCHAR;
8190         bookRequested = FALSE;
8191         /* Program may be pondering now */
8192         cps->maybeThinking = TRUE;
8193         if (cps->sendTime == 2) cps->sendTime = 1;
8194         if (cps->offeredDraw) cps->offeredDraw--;
8195
8196         /* [AS] Save move info*/
8197         pvInfoList[ forwardMostMove ].score = programStats.score;
8198         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8199         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8200
8201         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8202
8203         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8204         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8205             int count = 0;
8206
8207             while( count < adjudicateLossPlies ) {
8208                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8209
8210                 if( count & 1 ) {
8211                     score = -score; /* Flip score for winning side */
8212                 }
8213
8214                 if( score > adjudicateLossThreshold ) {
8215                     break;
8216                 }
8217
8218                 count++;
8219             }
8220
8221             if( count >= adjudicateLossPlies ) {
8222                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8223
8224                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8225                     "Xboard adjudication",
8226                     GE_XBOARD );
8227
8228                 return;
8229             }
8230         }
8231
8232         if(Adjudicate(cps)) {
8233             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8234             return; // [HGM] adjudicate: for all automatic game ends
8235         }
8236
8237 #if ZIPPY
8238         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8239             first.initDone) {
8240           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8241                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8242                 SendToICS("draw ");
8243                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8244           }
8245           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8246           ics_user_moved = 1;
8247           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8248                 char buf[3*MSG_SIZ];
8249
8250                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8251                         programStats.score / 100.,
8252                         programStats.depth,
8253                         programStats.time / 100.,
8254                         (unsigned int)programStats.nodes,
8255                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8256                         programStats.movelist);
8257                 SendToICS(buf);
8258 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8259           }
8260         }
8261 #endif
8262
8263         /* [AS] Clear stats for next move */
8264         ClearProgramStats();
8265         thinkOutput[0] = NULLCHAR;
8266         hiddenThinkOutputState = 0;
8267
8268         bookHit = NULL;
8269         if (gameMode == TwoMachinesPlay) {
8270             /* [HGM] relaying draw offers moved to after reception of move */
8271             /* and interpreting offer as claim if it brings draw condition */
8272             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8273                 SendToProgram("draw\n", cps->other);
8274             }
8275             if (cps->other->sendTime) {
8276                 SendTimeRemaining(cps->other,
8277                                   cps->other->twoMachinesColor[0] == 'w');
8278             }
8279             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8280             if (firstMove && !bookHit) {
8281                 firstMove = FALSE;
8282                 if (cps->other->useColors) {
8283                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8284                 }
8285                 SendToProgram("go\n", cps->other);
8286             }
8287             cps->other->maybeThinking = TRUE;
8288         }
8289
8290         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8291
8292         if (!pausing && appData.ringBellAfterMoves) {
8293             RingBell();
8294         }
8295
8296         /*
8297          * Reenable menu items that were disabled while
8298          * machine was thinking
8299          */
8300         if (gameMode != TwoMachinesPlay)
8301             SetUserThinkingEnables();
8302
8303         // [HGM] book: after book hit opponent has received move and is now in force mode
8304         // force the book reply into it, and then fake that it outputted this move by jumping
8305         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8306         if(bookHit) {
8307                 static char bookMove[MSG_SIZ]; // a bit generous?
8308
8309                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8310                 strcat(bookMove, bookHit);
8311                 message = bookMove;
8312                 cps = cps->other;
8313                 programStats.nodes = programStats.depth = programStats.time =
8314                 programStats.score = programStats.got_only_move = 0;
8315                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8316
8317                 if(cps->lastPing != cps->lastPong) {
8318                     savedMessage = message; // args for deferred call
8319                     savedState = cps;
8320                     ScheduleDelayedEvent(DeferredBookMove, 10);
8321                     return;
8322                 }
8323                 goto FakeBookMove;
8324         }
8325
8326         return;
8327     }
8328
8329     /* Set special modes for chess engines.  Later something general
8330      *  could be added here; for now there is just one kludge feature,
8331      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8332      *  when "xboard" is given as an interactive command.
8333      */
8334     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8335         cps->useSigint = FALSE;
8336         cps->useSigterm = FALSE;
8337     }
8338     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8339       ParseFeatures(message+8, cps);
8340       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8341     }
8342
8343     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8344                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8345       int dummy, s=6; char buf[MSG_SIZ];
8346       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8347       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8348       if(startedFromSetupPosition) return;
8349       ParseFEN(boards[0], &dummy, message+s);
8350       DrawPosition(TRUE, boards[0]);
8351       startedFromSetupPosition = TRUE;
8352       return;
8353     }
8354     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8355      * want this, I was asked to put it in, and obliged.
8356      */
8357     if (!strncmp(message, "setboard ", 9)) {
8358         Board initial_position;
8359
8360         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8361
8362         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8363             DisplayError(_("Bad FEN received from engine"), 0);
8364             return ;
8365         } else {
8366            Reset(TRUE, FALSE);
8367            CopyBoard(boards[0], initial_position);
8368            initialRulePlies = FENrulePlies;
8369            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8370            else gameMode = MachinePlaysBlack;
8371            DrawPosition(FALSE, boards[currentMove]);
8372         }
8373         return;
8374     }
8375
8376     /*
8377      * Look for communication commands
8378      */
8379     if (!strncmp(message, "telluser ", 9)) {
8380         if(message[9] == '\\' && message[10] == '\\')
8381             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8382         PlayTellSound();
8383         DisplayNote(message + 9);
8384         return;
8385     }
8386     if (!strncmp(message, "tellusererror ", 14)) {
8387         cps->userError = 1;
8388         if(message[14] == '\\' && message[15] == '\\')
8389             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8390         PlayTellSound();
8391         DisplayError(message + 14, 0);
8392         return;
8393     }
8394     if (!strncmp(message, "tellopponent ", 13)) {
8395       if (appData.icsActive) {
8396         if (loggedOn) {
8397           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8398           SendToICS(buf1);
8399         }
8400       } else {
8401         DisplayNote(message + 13);
8402       }
8403       return;
8404     }
8405     if (!strncmp(message, "tellothers ", 11)) {
8406       if (appData.icsActive) {
8407         if (loggedOn) {
8408           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8409           SendToICS(buf1);
8410         }
8411       }
8412       return;
8413     }
8414     if (!strncmp(message, "tellall ", 8)) {
8415       if (appData.icsActive) {
8416         if (loggedOn) {
8417           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8418           SendToICS(buf1);
8419         }
8420       } else {
8421         DisplayNote(message + 8);
8422       }
8423       return;
8424     }
8425     if (strncmp(message, "warning", 7) == 0) {
8426         /* Undocumented feature, use tellusererror in new code */
8427         DisplayError(message, 0);
8428         return;
8429     }
8430     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8431         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8432         strcat(realname, " query");
8433         AskQuestion(realname, buf2, buf1, cps->pr);
8434         return;
8435     }
8436     /* Commands from the engine directly to ICS.  We don't allow these to be
8437      *  sent until we are logged on. Crafty kibitzes have been known to
8438      *  interfere with the login process.
8439      */
8440     if (loggedOn) {
8441         if (!strncmp(message, "tellics ", 8)) {
8442             SendToICS(message + 8);
8443             SendToICS("\n");
8444             return;
8445         }
8446         if (!strncmp(message, "tellicsnoalias ", 15)) {
8447             SendToICS(ics_prefix);
8448             SendToICS(message + 15);
8449             SendToICS("\n");
8450             return;
8451         }
8452         /* The following are for backward compatibility only */
8453         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8454             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8455             SendToICS(ics_prefix);
8456             SendToICS(message);
8457             SendToICS("\n");
8458             return;
8459         }
8460     }
8461     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8462         return;
8463     }
8464     /*
8465      * If the move is illegal, cancel it and redraw the board.
8466      * Also deal with other error cases.  Matching is rather loose
8467      * here to accommodate engines written before the spec.
8468      */
8469     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8470         strncmp(message, "Error", 5) == 0) {
8471         if (StrStr(message, "name") ||
8472             StrStr(message, "rating") || StrStr(message, "?") ||
8473             StrStr(message, "result") || StrStr(message, "board") ||
8474             StrStr(message, "bk") || StrStr(message, "computer") ||
8475             StrStr(message, "variant") || StrStr(message, "hint") ||
8476             StrStr(message, "random") || StrStr(message, "depth") ||
8477             StrStr(message, "accepted")) {
8478             return;
8479         }
8480         if (StrStr(message, "protover")) {
8481           /* Program is responding to input, so it's apparently done
8482              initializing, and this error message indicates it is
8483              protocol version 1.  So we don't need to wait any longer
8484              for it to initialize and send feature commands. */
8485           FeatureDone(cps, 1);
8486           cps->protocolVersion = 1;
8487           return;
8488         }
8489         cps->maybeThinking = FALSE;
8490
8491         if (StrStr(message, "draw")) {
8492             /* Program doesn't have "draw" command */
8493             cps->sendDrawOffers = 0;
8494             return;
8495         }
8496         if (cps->sendTime != 1 &&
8497             (StrStr(message, "time") || StrStr(message, "otim"))) {
8498           /* Program apparently doesn't have "time" or "otim" command */
8499           cps->sendTime = 0;
8500           return;
8501         }
8502         if (StrStr(message, "analyze")) {
8503             cps->analysisSupport = FALSE;
8504             cps->analyzing = FALSE;
8505 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8506             EditGameEvent(); // [HGM] try to preserve loaded game
8507             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8508             DisplayError(buf2, 0);
8509             return;
8510         }
8511         if (StrStr(message, "(no matching move)st")) {
8512           /* Special kludge for GNU Chess 4 only */
8513           cps->stKludge = TRUE;
8514           SendTimeControl(cps, movesPerSession, timeControl,
8515                           timeIncrement, appData.searchDepth,
8516                           searchTime);
8517           return;
8518         }
8519         if (StrStr(message, "(no matching move)sd")) {
8520           /* Special kludge for GNU Chess 4 only */
8521           cps->sdKludge = TRUE;
8522           SendTimeControl(cps, movesPerSession, timeControl,
8523                           timeIncrement, appData.searchDepth,
8524                           searchTime);
8525           return;
8526         }
8527         if (!StrStr(message, "llegal")) {
8528             return;
8529         }
8530         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8531             gameMode == IcsIdle) return;
8532         if (forwardMostMove <= backwardMostMove) return;
8533         if (pausing) PauseEvent();
8534       if(appData.forceIllegal) {
8535             // [HGM] illegal: machine refused move; force position after move into it
8536           SendToProgram("force\n", cps);
8537           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8538                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8539                 // when black is to move, while there might be nothing on a2 or black
8540                 // might already have the move. So send the board as if white has the move.
8541                 // But first we must change the stm of the engine, as it refused the last move
8542                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8543                 if(WhiteOnMove(forwardMostMove)) {
8544                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8545                     SendBoard(cps, forwardMostMove); // kludgeless board
8546                 } else {
8547                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8548                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8549                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8550                 }
8551           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8552             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8553                  gameMode == TwoMachinesPlay)
8554               SendToProgram("go\n", cps);
8555             return;
8556       } else
8557         if (gameMode == PlayFromGameFile) {
8558             /* Stop reading this game file */
8559             gameMode = EditGame;
8560             ModeHighlight();
8561         }
8562         /* [HGM] illegal-move claim should forfeit game when Xboard */
8563         /* only passes fully legal moves                            */
8564         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8565             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8566                                 "False illegal-move claim", GE_XBOARD );
8567             return; // do not take back move we tested as valid
8568         }
8569         currentMove = forwardMostMove-1;
8570         DisplayMove(currentMove-1); /* before DisplayMoveError */
8571         SwitchClocks(forwardMostMove-1); // [HGM] race
8572         DisplayBothClocks();
8573         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8574                 parseList[currentMove], _(cps->which));
8575         DisplayMoveError(buf1);
8576         DrawPosition(FALSE, boards[currentMove]);
8577
8578         SetUserThinkingEnables();
8579         return;
8580     }
8581     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8582         /* Program has a broken "time" command that
8583            outputs a string not ending in newline.
8584            Don't use it. */
8585         cps->sendTime = 0;
8586     }
8587
8588     /*
8589      * If chess program startup fails, exit with an error message.
8590      * Attempts to recover here are futile. [HGM] Well, we try anyway
8591      */
8592     if ((StrStr(message, "unknown host") != NULL)
8593         || (StrStr(message, "No remote directory") != NULL)
8594         || (StrStr(message, "not found") != NULL)
8595         || (StrStr(message, "No such file") != NULL)
8596         || (StrStr(message, "can't alloc") != NULL)
8597         || (StrStr(message, "Permission denied") != NULL)) {
8598
8599         cps->maybeThinking = FALSE;
8600         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8601                 _(cps->which), cps->program, cps->host, message);
8602         RemoveInputSource(cps->isr);
8603         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8604             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8605             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8606         }
8607         return;
8608     }
8609
8610     /*
8611      * Look for hint output
8612      */
8613     if (sscanf(message, "Hint: %s", buf1) == 1) {
8614         if (cps == &first && hintRequested) {
8615             hintRequested = FALSE;
8616             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8617                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8618                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8619                                     PosFlags(forwardMostMove),
8620                                     fromY, fromX, toY, toX, promoChar, buf1);
8621                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8622                 DisplayInformation(buf2);
8623             } else {
8624                 /* Hint move could not be parsed!? */
8625               snprintf(buf2, sizeof(buf2),
8626                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8627                         buf1, _(cps->which));
8628                 DisplayError(buf2, 0);
8629             }
8630         } else {
8631           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8632         }
8633         return;
8634     }
8635
8636     /*
8637      * Ignore other messages if game is not in progress
8638      */
8639     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8640         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8641
8642     /*
8643      * look for win, lose, draw, or draw offer
8644      */
8645     if (strncmp(message, "1-0", 3) == 0) {
8646         char *p, *q, *r = "";
8647         p = strchr(message, '{');
8648         if (p) {
8649             q = strchr(p, '}');
8650             if (q) {
8651                 *q = NULLCHAR;
8652                 r = p + 1;
8653             }
8654         }
8655         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8656         return;
8657     } else if (strncmp(message, "0-1", 3) == 0) {
8658         char *p, *q, *r = "";
8659         p = strchr(message, '{');
8660         if (p) {
8661             q = strchr(p, '}');
8662             if (q) {
8663                 *q = NULLCHAR;
8664                 r = p + 1;
8665             }
8666         }
8667         /* Kludge for Arasan 4.1 bug */
8668         if (strcmp(r, "Black resigns") == 0) {
8669             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8670             return;
8671         }
8672         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8673         return;
8674     } else if (strncmp(message, "1/2", 3) == 0) {
8675         char *p, *q, *r = "";
8676         p = strchr(message, '{');
8677         if (p) {
8678             q = strchr(p, '}');
8679             if (q) {
8680                 *q = NULLCHAR;
8681                 r = p + 1;
8682             }
8683         }
8684
8685         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8686         return;
8687
8688     } else if (strncmp(message, "White resign", 12) == 0) {
8689         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8690         return;
8691     } else if (strncmp(message, "Black resign", 12) == 0) {
8692         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8693         return;
8694     } else if (strncmp(message, "White matches", 13) == 0 ||
8695                strncmp(message, "Black matches", 13) == 0   ) {
8696         /* [HGM] ignore GNUShogi noises */
8697         return;
8698     } else if (strncmp(message, "White", 5) == 0 &&
8699                message[5] != '(' &&
8700                StrStr(message, "Black") == NULL) {
8701         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8702         return;
8703     } else if (strncmp(message, "Black", 5) == 0 &&
8704                message[5] != '(') {
8705         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8706         return;
8707     } else if (strcmp(message, "resign") == 0 ||
8708                strcmp(message, "computer resigns") == 0) {
8709         switch (gameMode) {
8710           case MachinePlaysBlack:
8711           case IcsPlayingBlack:
8712             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8713             break;
8714           case MachinePlaysWhite:
8715           case IcsPlayingWhite:
8716             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8717             break;
8718           case TwoMachinesPlay:
8719             if (cps->twoMachinesColor[0] == 'w')
8720               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8721             else
8722               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8723             break;
8724           default:
8725             /* can't happen */
8726             break;
8727         }
8728         return;
8729     } else if (strncmp(message, "opponent mates", 14) == 0) {
8730         switch (gameMode) {
8731           case MachinePlaysBlack:
8732           case IcsPlayingBlack:
8733             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8734             break;
8735           case MachinePlaysWhite:
8736           case IcsPlayingWhite:
8737             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8738             break;
8739           case TwoMachinesPlay:
8740             if (cps->twoMachinesColor[0] == 'w')
8741               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8742             else
8743               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8744             break;
8745           default:
8746             /* can't happen */
8747             break;
8748         }
8749         return;
8750     } else if (strncmp(message, "computer mates", 14) == 0) {
8751         switch (gameMode) {
8752           case MachinePlaysBlack:
8753           case IcsPlayingBlack:
8754             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8755             break;
8756           case MachinePlaysWhite:
8757           case IcsPlayingWhite:
8758             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8759             break;
8760           case TwoMachinesPlay:
8761             if (cps->twoMachinesColor[0] == 'w')
8762               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8763             else
8764               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8765             break;
8766           default:
8767             /* can't happen */
8768             break;
8769         }
8770         return;
8771     } else if (strncmp(message, "checkmate", 9) == 0) {
8772         if (WhiteOnMove(forwardMostMove)) {
8773             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8774         } else {
8775             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8776         }
8777         return;
8778     } else if (strstr(message, "Draw") != NULL ||
8779                strstr(message, "game is a draw") != NULL) {
8780         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8781         return;
8782     } else if (strstr(message, "offer") != NULL &&
8783                strstr(message, "draw") != NULL) {
8784 #if ZIPPY
8785         if (appData.zippyPlay && first.initDone) {
8786             /* Relay offer to ICS */
8787             SendToICS(ics_prefix);
8788             SendToICS("draw\n");
8789         }
8790 #endif
8791         cps->offeredDraw = 2; /* valid until this engine moves twice */
8792         if (gameMode == TwoMachinesPlay) {
8793             if (cps->other->offeredDraw) {
8794                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8795             /* [HGM] in two-machine mode we delay relaying draw offer      */
8796             /* until after we also have move, to see if it is really claim */
8797             }
8798         } else if (gameMode == MachinePlaysWhite ||
8799                    gameMode == MachinePlaysBlack) {
8800           if (userOfferedDraw) {
8801             DisplayInformation(_("Machine accepts your draw offer"));
8802             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8803           } else {
8804             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8805           }
8806         }
8807     }
8808
8809
8810     /*
8811      * Look for thinking output
8812      */
8813     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8814           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8815                                 ) {
8816         int plylev, mvleft, mvtot, curscore, time;
8817         char mvname[MOVE_LEN];
8818         u64 nodes; // [DM]
8819         char plyext;
8820         int ignore = FALSE;
8821         int prefixHint = FALSE;
8822         mvname[0] = NULLCHAR;
8823
8824         switch (gameMode) {
8825           case MachinePlaysBlack:
8826           case IcsPlayingBlack:
8827             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8828             break;
8829           case MachinePlaysWhite:
8830           case IcsPlayingWhite:
8831             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8832             break;
8833           case AnalyzeMode:
8834           case AnalyzeFile:
8835             break;
8836           case IcsObserving: /* [DM] icsEngineAnalyze */
8837             if (!appData.icsEngineAnalyze) ignore = TRUE;
8838             break;
8839           case TwoMachinesPlay:
8840             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8841                 ignore = TRUE;
8842             }
8843             break;
8844           default:
8845             ignore = TRUE;
8846             break;
8847         }
8848
8849         if (!ignore) {
8850             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8851             buf1[0] = NULLCHAR;
8852             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8853                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8854
8855                 if (plyext != ' ' && plyext != '\t') {
8856                     time *= 100;
8857                 }
8858
8859                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8860                 if( cps->scoreIsAbsolute &&
8861                     ( gameMode == MachinePlaysBlack ||
8862                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8863                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8864                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8865                      !WhiteOnMove(currentMove)
8866                     ) )
8867                 {
8868                     curscore = -curscore;
8869                 }
8870
8871                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8872
8873                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8874                         char buf[MSG_SIZ];
8875                         FILE *f;
8876                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8877                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8878                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8879                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8880                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8881                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8882                                 fclose(f);
8883                         } else DisplayError(_("failed writing PV"), 0);
8884                 }
8885
8886                 tempStats.depth = plylev;
8887                 tempStats.nodes = nodes;
8888                 tempStats.time = time;
8889                 tempStats.score = curscore;
8890                 tempStats.got_only_move = 0;
8891
8892                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8893                         int ticklen;
8894
8895                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8896                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8897                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8898                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8899                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8900                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8901                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8902                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8903                 }
8904
8905                 /* Buffer overflow protection */
8906                 if (pv[0] != NULLCHAR) {
8907                     if (strlen(pv) >= sizeof(tempStats.movelist)
8908                         && appData.debugMode) {
8909                         fprintf(debugFP,
8910                                 "PV is too long; using the first %u bytes.\n",
8911                                 (unsigned) sizeof(tempStats.movelist) - 1);
8912                     }
8913
8914                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8915                 } else {
8916                     sprintf(tempStats.movelist, " no PV\n");
8917                 }
8918
8919                 if (tempStats.seen_stat) {
8920                     tempStats.ok_to_send = 1;
8921                 }
8922
8923                 if (strchr(tempStats.movelist, '(') != NULL) {
8924                     tempStats.line_is_book = 1;
8925                     tempStats.nr_moves = 0;
8926                     tempStats.moves_left = 0;
8927                 } else {
8928                     tempStats.line_is_book = 0;
8929                 }
8930
8931                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8932                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8933
8934                 SendProgramStatsToFrontend( cps, &tempStats );
8935
8936                 /*
8937                     [AS] Protect the thinkOutput buffer from overflow... this
8938                     is only useful if buf1 hasn't overflowed first!
8939                 */
8940                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8941                          plylev,
8942                          (gameMode == TwoMachinesPlay ?
8943                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8944                          ((double) curscore) / 100.0,
8945                          prefixHint ? lastHint : "",
8946                          prefixHint ? " " : "" );
8947
8948                 if( buf1[0] != NULLCHAR ) {
8949                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8950
8951                     if( strlen(pv) > max_len ) {
8952                         if( appData.debugMode) {
8953                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8954                         }
8955                         pv[max_len+1] = '\0';
8956                     }
8957
8958                     strcat( thinkOutput, pv);
8959                 }
8960
8961                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8962                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8963                     DisplayMove(currentMove - 1);
8964                 }
8965                 return;
8966
8967             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8968                 /* crafty (9.25+) says "(only move) <move>"
8969                  * if there is only 1 legal move
8970                  */
8971                 sscanf(p, "(only move) %s", buf1);
8972                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8973                 sprintf(programStats.movelist, "%s (only move)", buf1);
8974                 programStats.depth = 1;
8975                 programStats.nr_moves = 1;
8976                 programStats.moves_left = 1;
8977                 programStats.nodes = 1;
8978                 programStats.time = 1;
8979                 programStats.got_only_move = 1;
8980
8981                 /* Not really, but we also use this member to
8982                    mean "line isn't going to change" (Crafty
8983                    isn't searching, so stats won't change) */
8984                 programStats.line_is_book = 1;
8985
8986                 SendProgramStatsToFrontend( cps, &programStats );
8987
8988                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8989                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8990                     DisplayMove(currentMove - 1);
8991                 }
8992                 return;
8993             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8994                               &time, &nodes, &plylev, &mvleft,
8995                               &mvtot, mvname) >= 5) {
8996                 /* The stat01: line is from Crafty (9.29+) in response
8997                    to the "." command */
8998                 programStats.seen_stat = 1;
8999                 cps->maybeThinking = TRUE;
9000
9001                 if (programStats.got_only_move || !appData.periodicUpdates)
9002                   return;
9003
9004                 programStats.depth = plylev;
9005                 programStats.time = time;
9006                 programStats.nodes = nodes;
9007                 programStats.moves_left = mvleft;
9008                 programStats.nr_moves = mvtot;
9009                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9010                 programStats.ok_to_send = 1;
9011                 programStats.movelist[0] = '\0';
9012
9013                 SendProgramStatsToFrontend( cps, &programStats );
9014
9015                 return;
9016
9017             } else if (strncmp(message,"++",2) == 0) {
9018                 /* Crafty 9.29+ outputs this */
9019                 programStats.got_fail = 2;
9020                 return;
9021
9022             } else if (strncmp(message,"--",2) == 0) {
9023                 /* Crafty 9.29+ outputs this */
9024                 programStats.got_fail = 1;
9025                 return;
9026
9027             } else if (thinkOutput[0] != NULLCHAR &&
9028                        strncmp(message, "    ", 4) == 0) {
9029                 unsigned message_len;
9030
9031                 p = message;
9032                 while (*p && *p == ' ') p++;
9033
9034                 message_len = strlen( p );
9035
9036                 /* [AS] Avoid buffer overflow */
9037                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9038                     strcat(thinkOutput, " ");
9039                     strcat(thinkOutput, p);
9040                 }
9041
9042                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9043                     strcat(programStats.movelist, " ");
9044                     strcat(programStats.movelist, p);
9045                 }
9046
9047                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9048                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9049                     DisplayMove(currentMove - 1);
9050                 }
9051                 return;
9052             }
9053         }
9054         else {
9055             buf1[0] = NULLCHAR;
9056
9057             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9058                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9059             {
9060                 ChessProgramStats cpstats;
9061
9062                 if (plyext != ' ' && plyext != '\t') {
9063                     time *= 100;
9064                 }
9065
9066                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9067                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9068                     curscore = -curscore;
9069                 }
9070
9071                 cpstats.depth = plylev;
9072                 cpstats.nodes = nodes;
9073                 cpstats.time = time;
9074                 cpstats.score = curscore;
9075                 cpstats.got_only_move = 0;
9076                 cpstats.movelist[0] = '\0';
9077
9078                 if (buf1[0] != NULLCHAR) {
9079                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9080                 }
9081
9082                 cpstats.ok_to_send = 0;
9083                 cpstats.line_is_book = 0;
9084                 cpstats.nr_moves = 0;
9085                 cpstats.moves_left = 0;
9086
9087                 SendProgramStatsToFrontend( cps, &cpstats );
9088             }
9089         }
9090     }
9091 }
9092
9093
9094 /* Parse a game score from the character string "game", and
9095    record it as the history of the current game.  The game
9096    score is NOT assumed to start from the standard position.
9097    The display is not updated in any way.
9098    */
9099 void
9100 ParseGameHistory (char *game)
9101 {
9102     ChessMove moveType;
9103     int fromX, fromY, toX, toY, boardIndex;
9104     char promoChar;
9105     char *p, *q;
9106     char buf[MSG_SIZ];
9107
9108     if (appData.debugMode)
9109       fprintf(debugFP, "Parsing game history: %s\n", game);
9110
9111     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9112     gameInfo.site = StrSave(appData.icsHost);
9113     gameInfo.date = PGNDate();
9114     gameInfo.round = StrSave("-");
9115
9116     /* Parse out names of players */
9117     while (*game == ' ') game++;
9118     p = buf;
9119     while (*game != ' ') *p++ = *game++;
9120     *p = NULLCHAR;
9121     gameInfo.white = StrSave(buf);
9122     while (*game == ' ') game++;
9123     p = buf;
9124     while (*game != ' ' && *game != '\n') *p++ = *game++;
9125     *p = NULLCHAR;
9126     gameInfo.black = StrSave(buf);
9127
9128     /* Parse moves */
9129     boardIndex = blackPlaysFirst ? 1 : 0;
9130     yynewstr(game);
9131     for (;;) {
9132         yyboardindex = boardIndex;
9133         moveType = (ChessMove) Myylex();
9134         switch (moveType) {
9135           case IllegalMove:             /* maybe suicide chess, etc. */
9136   if (appData.debugMode) {
9137     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9138     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9139     setbuf(debugFP, NULL);
9140   }
9141           case WhitePromotion:
9142           case BlackPromotion:
9143           case WhiteNonPromotion:
9144           case BlackNonPromotion:
9145           case NormalMove:
9146           case WhiteCapturesEnPassant:
9147           case BlackCapturesEnPassant:
9148           case WhiteKingSideCastle:
9149           case WhiteQueenSideCastle:
9150           case BlackKingSideCastle:
9151           case BlackQueenSideCastle:
9152           case WhiteKingSideCastleWild:
9153           case WhiteQueenSideCastleWild:
9154           case BlackKingSideCastleWild:
9155           case BlackQueenSideCastleWild:
9156           /* PUSH Fabien */
9157           case WhiteHSideCastleFR:
9158           case WhiteASideCastleFR:
9159           case BlackHSideCastleFR:
9160           case BlackASideCastleFR:
9161           /* POP Fabien */
9162             fromX = currentMoveString[0] - AAA;
9163             fromY = currentMoveString[1] - ONE;
9164             toX = currentMoveString[2] - AAA;
9165             toY = currentMoveString[3] - ONE;
9166             promoChar = currentMoveString[4];
9167             break;
9168           case WhiteDrop:
9169           case BlackDrop:
9170             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9171             fromX = moveType == WhiteDrop ?
9172               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9173             (int) CharToPiece(ToLower(currentMoveString[0]));
9174             fromY = DROP_RANK;
9175             toX = currentMoveString[2] - AAA;
9176             toY = currentMoveString[3] - ONE;
9177             promoChar = NULLCHAR;
9178             break;
9179           case AmbiguousMove:
9180             /* bug? */
9181             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9182   if (appData.debugMode) {
9183     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9184     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9185     setbuf(debugFP, NULL);
9186   }
9187             DisplayError(buf, 0);
9188             return;
9189           case ImpossibleMove:
9190             /* bug? */
9191             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9192   if (appData.debugMode) {
9193     fprintf(debugFP, "Impossible 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 EndOfFile:
9200             if (boardIndex < backwardMostMove) {
9201                 /* Oops, gap.  How did that happen? */
9202                 DisplayError(_("Gap in move list"), 0);
9203                 return;
9204             }
9205             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9206             if (boardIndex > forwardMostMove) {
9207                 forwardMostMove = boardIndex;
9208             }
9209             return;
9210           case ElapsedTime:
9211             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9212                 strcat(parseList[boardIndex-1], " ");
9213                 strcat(parseList[boardIndex-1], yy_text);
9214             }
9215             continue;
9216           case Comment:
9217           case PGNTag:
9218           case NAG:
9219           default:
9220             /* ignore */
9221             continue;
9222           case WhiteWins:
9223           case BlackWins:
9224           case GameIsDrawn:
9225           case GameUnfinished:
9226             if (gameMode == IcsExamining) {
9227                 if (boardIndex < backwardMostMove) {
9228                     /* Oops, gap.  How did that happen? */
9229                     return;
9230                 }
9231                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9232                 return;
9233             }
9234             gameInfo.result = moveType;
9235             p = strchr(yy_text, '{');
9236             if (p == NULL) p = strchr(yy_text, '(');
9237             if (p == NULL) {
9238                 p = yy_text;
9239                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9240             } else {
9241                 q = strchr(p, *p == '{' ? '}' : ')');
9242                 if (q != NULL) *q = NULLCHAR;
9243                 p++;
9244             }
9245             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9246             gameInfo.resultDetails = StrSave(p);
9247             continue;
9248         }
9249         if (boardIndex >= forwardMostMove &&
9250             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9251             backwardMostMove = blackPlaysFirst ? 1 : 0;
9252             return;
9253         }
9254         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9255                                  fromY, fromX, toY, toX, promoChar,
9256                                  parseList[boardIndex]);
9257         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9258         /* currentMoveString is set as a side-effect of yylex */
9259         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9260         strcat(moveList[boardIndex], "\n");
9261         boardIndex++;
9262         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9263         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9264           case MT_NONE:
9265           case MT_STALEMATE:
9266           default:
9267             break;
9268           case MT_CHECK:
9269             if(gameInfo.variant != VariantShogi)
9270                 strcat(parseList[boardIndex - 1], "+");
9271             break;
9272           case MT_CHECKMATE:
9273           case MT_STAINMATE:
9274             strcat(parseList[boardIndex - 1], "#");
9275             break;
9276         }
9277     }
9278 }
9279
9280
9281 /* Apply a move to the given board  */
9282 void
9283 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9284 {
9285   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9286   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9287
9288     /* [HGM] compute & store e.p. status and castling rights for new position */
9289     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9290
9291       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9292       oldEP = (signed char)board[EP_STATUS];
9293       board[EP_STATUS] = EP_NONE;
9294
9295   if (fromY == DROP_RANK) {
9296         /* must be first */
9297         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9298             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9299             return;
9300         }
9301         piece = board[toY][toX] = (ChessSquare) fromX;
9302   } else {
9303       int i;
9304
9305       if( board[toY][toX] != EmptySquare )
9306            board[EP_STATUS] = EP_CAPTURE;
9307
9308       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9309            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9310                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9311       } else
9312       if( board[fromY][fromX] == WhitePawn ) {
9313            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9314                board[EP_STATUS] = EP_PAWN_MOVE;
9315            if( toY-fromY==2) {
9316                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9317                         gameInfo.variant != VariantBerolina || toX < fromX)
9318                       board[EP_STATUS] = toX | berolina;
9319                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9320                         gameInfo.variant != VariantBerolina || toX > fromX)
9321                       board[EP_STATUS] = toX;
9322            }
9323       } else
9324       if( board[fromY][fromX] == BlackPawn ) {
9325            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9326                board[EP_STATUS] = EP_PAWN_MOVE;
9327            if( toY-fromY== -2) {
9328                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9329                         gameInfo.variant != VariantBerolina || toX < fromX)
9330                       board[EP_STATUS] = toX | berolina;
9331                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9332                         gameInfo.variant != VariantBerolina || toX > fromX)
9333                       board[EP_STATUS] = toX;
9334            }
9335        }
9336
9337        for(i=0; i<nrCastlingRights; i++) {
9338            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9339               board[CASTLING][i] == toX   && castlingRank[i] == toY
9340              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9341        }
9342
9343        if(gameInfo.variant == VariantSChess) { // update virginity
9344            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9345            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9346            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9347            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9348        }
9349
9350      if (fromX == toX && fromY == toY) return;
9351
9352      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9353      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9354      if(gameInfo.variant == VariantKnightmate)
9355          king += (int) WhiteUnicorn - (int) WhiteKing;
9356
9357     /* Code added by Tord: */
9358     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9359     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9360         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9361       board[fromY][fromX] = EmptySquare;
9362       board[toY][toX] = EmptySquare;
9363       if((toX > fromX) != (piece == WhiteRook)) {
9364         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9365       } else {
9366         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9367       }
9368     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9369                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9370       board[fromY][fromX] = EmptySquare;
9371       board[toY][toX] = EmptySquare;
9372       if((toX > fromX) != (piece == BlackRook)) {
9373         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9374       } else {
9375         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9376       }
9377     /* End of code added by Tord */
9378
9379     } else if (board[fromY][fromX] == king
9380         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9381         && toY == fromY && toX > fromX+1) {
9382         board[fromY][fromX] = EmptySquare;
9383         board[toY][toX] = king;
9384         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9385         board[fromY][BOARD_RGHT-1] = EmptySquare;
9386     } else if (board[fromY][fromX] == king
9387         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9388                && toY == fromY && toX < fromX-1) {
9389         board[fromY][fromX] = EmptySquare;
9390         board[toY][toX] = king;
9391         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9392         board[fromY][BOARD_LEFT] = EmptySquare;
9393     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9394                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9395                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9396                ) {
9397         /* white pawn promotion */
9398         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9399         if(gameInfo.variant==VariantBughouse ||
9400            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9401             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9402         board[fromY][fromX] = EmptySquare;
9403     } else if ((fromY >= BOARD_HEIGHT>>1)
9404                && (toX != fromX)
9405                && gameInfo.variant != VariantXiangqi
9406                && gameInfo.variant != VariantBerolina
9407                && (board[fromY][fromX] == WhitePawn)
9408                && (board[toY][toX] == EmptySquare)) {
9409         board[fromY][fromX] = EmptySquare;
9410         board[toY][toX] = WhitePawn;
9411         captured = board[toY - 1][toX];
9412         board[toY - 1][toX] = EmptySquare;
9413     } else if ((fromY == BOARD_HEIGHT-4)
9414                && (toX == fromX)
9415                && gameInfo.variant == VariantBerolina
9416                && (board[fromY][fromX] == WhitePawn)
9417                && (board[toY][toX] == EmptySquare)) {
9418         board[fromY][fromX] = EmptySquare;
9419         board[toY][toX] = WhitePawn;
9420         if(oldEP & EP_BEROLIN_A) {
9421                 captured = board[fromY][fromX-1];
9422                 board[fromY][fromX-1] = EmptySquare;
9423         }else{  captured = board[fromY][fromX+1];
9424                 board[fromY][fromX+1] = EmptySquare;
9425         }
9426     } else if (board[fromY][fromX] == king
9427         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9428                && toY == fromY && toX > fromX+1) {
9429         board[fromY][fromX] = EmptySquare;
9430         board[toY][toX] = king;
9431         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9432         board[fromY][BOARD_RGHT-1] = EmptySquare;
9433     } else if (board[fromY][fromX] == king
9434         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9435                && toY == fromY && toX < fromX-1) {
9436         board[fromY][fromX] = EmptySquare;
9437         board[toY][toX] = king;
9438         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9439         board[fromY][BOARD_LEFT] = EmptySquare;
9440     } else if (fromY == 7 && fromX == 3
9441                && board[fromY][fromX] == BlackKing
9442                && toY == 7 && toX == 5) {
9443         board[fromY][fromX] = EmptySquare;
9444         board[toY][toX] = BlackKing;
9445         board[fromY][7] = EmptySquare;
9446         board[toY][4] = BlackRook;
9447     } else if (fromY == 7 && fromX == 3
9448                && board[fromY][fromX] == BlackKing
9449                && toY == 7 && toX == 1) {
9450         board[fromY][fromX] = EmptySquare;
9451         board[toY][toX] = BlackKing;
9452         board[fromY][0] = EmptySquare;
9453         board[toY][2] = BlackRook;
9454     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9455                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9456                && toY < promoRank && promoChar
9457                ) {
9458         /* black pawn promotion */
9459         board[toY][toX] = CharToPiece(ToLower(promoChar));
9460         if(gameInfo.variant==VariantBughouse ||
9461            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9462             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9463         board[fromY][fromX] = EmptySquare;
9464     } else if ((fromY < BOARD_HEIGHT>>1)
9465                && (toX != fromX)
9466                && gameInfo.variant != VariantXiangqi
9467                && gameInfo.variant != VariantBerolina
9468                && (board[fromY][fromX] == BlackPawn)
9469                && (board[toY][toX] == EmptySquare)) {
9470         board[fromY][fromX] = EmptySquare;
9471         board[toY][toX] = BlackPawn;
9472         captured = board[toY + 1][toX];
9473         board[toY + 1][toX] = EmptySquare;
9474     } else if ((fromY == 3)
9475                && (toX == fromX)
9476                && gameInfo.variant == VariantBerolina
9477                && (board[fromY][fromX] == BlackPawn)
9478                && (board[toY][toX] == EmptySquare)) {
9479         board[fromY][fromX] = EmptySquare;
9480         board[toY][toX] = BlackPawn;
9481         if(oldEP & EP_BEROLIN_A) {
9482                 captured = board[fromY][fromX-1];
9483                 board[fromY][fromX-1] = EmptySquare;
9484         }else{  captured = board[fromY][fromX+1];
9485                 board[fromY][fromX+1] = EmptySquare;
9486         }
9487     } else {
9488         board[toY][toX] = board[fromY][fromX];
9489         board[fromY][fromX] = EmptySquare;
9490     }
9491   }
9492
9493     if (gameInfo.holdingsWidth != 0) {
9494
9495       /* !!A lot more code needs to be written to support holdings  */
9496       /* [HGM] OK, so I have written it. Holdings are stored in the */
9497       /* penultimate board files, so they are automaticlly stored   */
9498       /* in the game history.                                       */
9499       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9500                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9501         /* Delete from holdings, by decreasing count */
9502         /* and erasing image if necessary            */
9503         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9504         if(p < (int) BlackPawn) { /* white drop */
9505              p -= (int)WhitePawn;
9506                  p = PieceToNumber((ChessSquare)p);
9507              if(p >= gameInfo.holdingsSize) p = 0;
9508              if(--board[p][BOARD_WIDTH-2] <= 0)
9509                   board[p][BOARD_WIDTH-1] = EmptySquare;
9510              if((int)board[p][BOARD_WIDTH-2] < 0)
9511                         board[p][BOARD_WIDTH-2] = 0;
9512         } else {                  /* black drop */
9513              p -= (int)BlackPawn;
9514                  p = PieceToNumber((ChessSquare)p);
9515              if(p >= gameInfo.holdingsSize) p = 0;
9516              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9517                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9518              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9519                         board[BOARD_HEIGHT-1-p][1] = 0;
9520         }
9521       }
9522       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9523           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9524         /* [HGM] holdings: Add to holdings, if holdings exist */
9525         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9526                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9527                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9528         }
9529         p = (int) captured;
9530         if (p >= (int) BlackPawn) {
9531           p -= (int)BlackPawn;
9532           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9533                   /* in Shogi restore piece to its original  first */
9534                   captured = (ChessSquare) (DEMOTED captured);
9535                   p = DEMOTED p;
9536           }
9537           p = PieceToNumber((ChessSquare)p);
9538           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9539           board[p][BOARD_WIDTH-2]++;
9540           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9541         } else {
9542           p -= (int)WhitePawn;
9543           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9544                   captured = (ChessSquare) (DEMOTED captured);
9545                   p = DEMOTED p;
9546           }
9547           p = PieceToNumber((ChessSquare)p);
9548           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9549           board[BOARD_HEIGHT-1-p][1]++;
9550           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9551         }
9552       }
9553     } else if (gameInfo.variant == VariantAtomic) {
9554       if (captured != EmptySquare) {
9555         int y, x;
9556         for (y = toY-1; y <= toY+1; y++) {
9557           for (x = toX-1; x <= toX+1; x++) {
9558             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9559                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9560               board[y][x] = EmptySquare;
9561             }
9562           }
9563         }
9564         board[toY][toX] = EmptySquare;
9565       }
9566     }
9567     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9568         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9569     } else
9570     if(promoChar == '+') {
9571         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9572         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9573     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9574         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9575         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9576            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9577         board[toY][toX] = newPiece;
9578     }
9579     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9580                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9581         // [HGM] superchess: take promotion piece out of holdings
9582         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9583         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9584             if(!--board[k][BOARD_WIDTH-2])
9585                 board[k][BOARD_WIDTH-1] = EmptySquare;
9586         } else {
9587             if(!--board[BOARD_HEIGHT-1-k][1])
9588                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9589         }
9590     }
9591
9592 }
9593
9594 /* Updates forwardMostMove */
9595 void
9596 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9597 {
9598 //    forwardMostMove++; // [HGM] bare: moved downstream
9599
9600     (void) CoordsToAlgebraic(boards[forwardMostMove],
9601                              PosFlags(forwardMostMove),
9602                              fromY, fromX, toY, toX, promoChar,
9603                              parseList[forwardMostMove]);
9604
9605     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9606         int timeLeft; static int lastLoadFlag=0; int king, piece;
9607         piece = boards[forwardMostMove][fromY][fromX];
9608         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9609         if(gameInfo.variant == VariantKnightmate)
9610             king += (int) WhiteUnicorn - (int) WhiteKing;
9611         if(forwardMostMove == 0) {
9612             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9613                 fprintf(serverMoves, "%s;", UserName());
9614             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9615                 fprintf(serverMoves, "%s;", second.tidy);
9616             fprintf(serverMoves, "%s;", first.tidy);
9617             if(gameMode == MachinePlaysWhite)
9618                 fprintf(serverMoves, "%s;", UserName());
9619             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9620                 fprintf(serverMoves, "%s;", second.tidy);
9621         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9622         lastLoadFlag = loadFlag;
9623         // print base move
9624         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9625         // print castling suffix
9626         if( toY == fromY && piece == king ) {
9627             if(toX-fromX > 1)
9628                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9629             if(fromX-toX >1)
9630                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9631         }
9632         // e.p. suffix
9633         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9634              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9635              boards[forwardMostMove][toY][toX] == EmptySquare
9636              && fromX != toX && fromY != toY)
9637                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9638         // promotion suffix
9639         if(promoChar != NULLCHAR) {
9640             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9641                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9642                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9643             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9644         }
9645         if(!loadFlag) {
9646                 char buf[MOVE_LEN*2], *p; int len;
9647             fprintf(serverMoves, "/%d/%d",
9648                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9649             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9650             else                      timeLeft = blackTimeRemaining/1000;
9651             fprintf(serverMoves, "/%d", timeLeft);
9652                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9653                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9654                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9655                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9656             fprintf(serverMoves, "/%s", buf);
9657         }
9658         fflush(serverMoves);
9659     }
9660
9661     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9662         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9663       return;
9664     }
9665     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9666     if (commentList[forwardMostMove+1] != NULL) {
9667         free(commentList[forwardMostMove+1]);
9668         commentList[forwardMostMove+1] = NULL;
9669     }
9670     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9671     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9672     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9673     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9674     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9675     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9676     adjustedClock = FALSE;
9677     gameInfo.result = GameUnfinished;
9678     if (gameInfo.resultDetails != NULL) {
9679         free(gameInfo.resultDetails);
9680         gameInfo.resultDetails = NULL;
9681     }
9682     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9683                               moveList[forwardMostMove - 1]);
9684     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9685       case MT_NONE:
9686       case MT_STALEMATE:
9687       default:
9688         break;
9689       case MT_CHECK:
9690         if(gameInfo.variant != VariantShogi)
9691             strcat(parseList[forwardMostMove - 1], "+");
9692         break;
9693       case MT_CHECKMATE:
9694       case MT_STAINMATE:
9695         strcat(parseList[forwardMostMove - 1], "#");
9696         break;
9697     }
9698
9699 }
9700
9701 /* Updates currentMove if not pausing */
9702 void
9703 ShowMove (int fromX, int fromY, int toX, int toY)
9704 {
9705     int instant = (gameMode == PlayFromGameFile) ?
9706         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9707     if(appData.noGUI) return;
9708     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9709         if (!instant) {
9710             if (forwardMostMove == currentMove + 1) {
9711                 AnimateMove(boards[forwardMostMove - 1],
9712                             fromX, fromY, toX, toY);
9713             }
9714             if (appData.highlightLastMove) {
9715                 SetHighlights(fromX, fromY, toX, toY);
9716             }
9717         }
9718         currentMove = forwardMostMove;
9719     }
9720
9721     if (instant) return;
9722
9723     DisplayMove(currentMove - 1);
9724     DrawPosition(FALSE, boards[currentMove]);
9725     DisplayBothClocks();
9726     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9727 }
9728
9729 void
9730 SendEgtPath (ChessProgramState *cps)
9731 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9732         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9733
9734         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9735
9736         while(*p) {
9737             char c, *q = name+1, *r, *s;
9738
9739             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9740             while(*p && *p != ',') *q++ = *p++;
9741             *q++ = ':'; *q = 0;
9742             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9743                 strcmp(name, ",nalimov:") == 0 ) {
9744                 // take nalimov path from the menu-changeable option first, if it is defined
9745               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9746                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9747             } else
9748             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9749                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9750                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9751                 s = r = StrStr(s, ":") + 1; // beginning of path info
9752                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9753                 c = *r; *r = 0;             // temporarily null-terminate path info
9754                     *--q = 0;               // strip of trailig ':' from name
9755                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9756                 *r = c;
9757                 SendToProgram(buf,cps);     // send egtbpath command for this format
9758             }
9759             if(*p == ',') p++; // read away comma to position for next format name
9760         }
9761 }
9762
9763 void
9764 InitChessProgram (ChessProgramState *cps, int setup)
9765 /* setup needed to setup FRC opening position */
9766 {
9767     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9768     if (appData.noChessProgram) return;
9769     hintRequested = FALSE;
9770     bookRequested = FALSE;
9771
9772     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9773     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9774     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9775     if(cps->memSize) { /* [HGM] memory */
9776       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9777         SendToProgram(buf, cps);
9778     }
9779     SendEgtPath(cps); /* [HGM] EGT */
9780     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9781       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9782         SendToProgram(buf, cps);
9783     }
9784
9785     SendToProgram(cps->initString, cps);
9786     if (gameInfo.variant != VariantNormal &&
9787         gameInfo.variant != VariantLoadable
9788         /* [HGM] also send variant if board size non-standard */
9789         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9790                                             ) {
9791       char *v = VariantName(gameInfo.variant);
9792       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9793         /* [HGM] in protocol 1 we have to assume all variants valid */
9794         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9795         DisplayFatalError(buf, 0, 1);
9796         return;
9797       }
9798
9799       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9800       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9801       if( gameInfo.variant == VariantXiangqi )
9802            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9803       if( gameInfo.variant == VariantShogi )
9804            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9805       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9806            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9807       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9808           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9809            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9810       if( gameInfo.variant == VariantCourier )
9811            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9812       if( gameInfo.variant == VariantSuper )
9813            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9814       if( gameInfo.variant == VariantGreat )
9815            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9816       if( gameInfo.variant == VariantSChess )
9817            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9818       if( gameInfo.variant == VariantGrand )
9819            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9820
9821       if(overruled) {
9822         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9823                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9824            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9825            if(StrStr(cps->variants, b) == NULL) {
9826                // specific sized variant not known, check if general sizing allowed
9827                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9828                    if(StrStr(cps->variants, "boardsize") == NULL) {
9829                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9830                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9831                        DisplayFatalError(buf, 0, 1);
9832                        return;
9833                    }
9834                    /* [HGM] here we really should compare with the maximum supported board size */
9835                }
9836            }
9837       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9838       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9839       SendToProgram(buf, cps);
9840     }
9841     currentlyInitializedVariant = gameInfo.variant;
9842
9843     /* [HGM] send opening position in FRC to first engine */
9844     if(setup) {
9845           SendToProgram("force\n", cps);
9846           SendBoard(cps, 0);
9847           /* engine is now in force mode! Set flag to wake it up after first move. */
9848           setboardSpoiledMachineBlack = 1;
9849     }
9850
9851     if (cps->sendICS) {
9852       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9853       SendToProgram(buf, cps);
9854     }
9855     cps->maybeThinking = FALSE;
9856     cps->offeredDraw = 0;
9857     if (!appData.icsActive) {
9858         SendTimeControl(cps, movesPerSession, timeControl,
9859                         timeIncrement, appData.searchDepth,
9860                         searchTime);
9861     }
9862     if (appData.showThinking
9863         // [HGM] thinking: four options require thinking output to be sent
9864         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9865                                 ) {
9866         SendToProgram("post\n", cps);
9867     }
9868     SendToProgram("hard\n", cps);
9869     if (!appData.ponderNextMove) {
9870         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9871            it without being sure what state we are in first.  "hard"
9872            is not a toggle, so that one is OK.
9873          */
9874         SendToProgram("easy\n", cps);
9875     }
9876     if (cps->usePing) {
9877       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9878       SendToProgram(buf, cps);
9879     }
9880     cps->initDone = TRUE;
9881     ClearEngineOutputPane(cps == &second);
9882 }
9883
9884
9885 void
9886 StartChessProgram (ChessProgramState *cps)
9887 {
9888     char buf[MSG_SIZ];
9889     int err;
9890
9891     if (appData.noChessProgram) return;
9892     cps->initDone = FALSE;
9893
9894     if (strcmp(cps->host, "localhost") == 0) {
9895         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9896     } else if (*appData.remoteShell == NULLCHAR) {
9897         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9898     } else {
9899         if (*appData.remoteUser == NULLCHAR) {
9900           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9901                     cps->program);
9902         } else {
9903           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9904                     cps->host, appData.remoteUser, cps->program);
9905         }
9906         err = StartChildProcess(buf, "", &cps->pr);
9907     }
9908
9909     if (err != 0) {
9910       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9911         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9912         if(cps != &first) return;
9913         appData.noChessProgram = TRUE;
9914         ThawUI();
9915         SetNCPMode();
9916 //      DisplayFatalError(buf, err, 1);
9917 //      cps->pr = NoProc;
9918 //      cps->isr = NULL;
9919         return;
9920     }
9921
9922     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9923     if (cps->protocolVersion > 1) {
9924       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9925       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9926       cps->comboCnt = 0;  //                and values of combo boxes
9927       SendToProgram(buf, cps);
9928     } else {
9929       SendToProgram("xboard\n", cps);
9930     }
9931 }
9932
9933 void
9934 TwoMachinesEventIfReady P((void))
9935 {
9936   static int curMess = 0;
9937   if (first.lastPing != first.lastPong) {
9938     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9939     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9940     return;
9941   }
9942   if (second.lastPing != second.lastPong) {
9943     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9944     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9945     return;
9946   }
9947   DisplayMessage("", ""); curMess = 0;
9948   ThawUI();
9949   TwoMachinesEvent();
9950 }
9951
9952 char *
9953 MakeName (char *template)
9954 {
9955     time_t clock;
9956     struct tm *tm;
9957     static char buf[MSG_SIZ];
9958     char *p = buf;
9959     int i;
9960
9961     clock = time((time_t *)NULL);
9962     tm = localtime(&clock);
9963
9964     while(*p++ = *template++) if(p[-1] == '%') {
9965         switch(*template++) {
9966           case 0:   *p = 0; return buf;
9967           case 'Y': i = tm->tm_year+1900; break;
9968           case 'y': i = tm->tm_year-100; break;
9969           case 'M': i = tm->tm_mon+1; break;
9970           case 'd': i = tm->tm_mday; break;
9971           case 'h': i = tm->tm_hour; break;
9972           case 'm': i = tm->tm_min; break;
9973           case 's': i = tm->tm_sec; break;
9974           default:  i = 0;
9975         }
9976         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9977     }
9978     return buf;
9979 }
9980
9981 int
9982 CountPlayers (char *p)
9983 {
9984     int n = 0;
9985     while(p = strchr(p, '\n')) p++, n++; // count participants
9986     return n;
9987 }
9988
9989 FILE *
9990 WriteTourneyFile (char *results, FILE *f)
9991 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9992     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9993     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9994         // create a file with tournament description
9995         fprintf(f, "-participants {%s}\n", appData.participants);
9996         fprintf(f, "-seedBase %d\n", appData.seedBase);
9997         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9998         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9999         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10000         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10001         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10002         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10003         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10004         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10005         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10006         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10007         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10008         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10009         if(searchTime > 0)
10010                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10011         else {
10012                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10013                 fprintf(f, "-tc %s\n", appData.timeControl);
10014                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10015         }
10016         fprintf(f, "-results \"%s\"\n", results);
10017     }
10018     return f;
10019 }
10020
10021 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10022
10023 void
10024 Substitute (char *participants, int expunge)
10025 {
10026     int i, changed, changes=0, nPlayers=0;
10027     char *p, *q, *r, buf[MSG_SIZ];
10028     if(participants == NULL) return;
10029     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10030     r = p = participants; q = appData.participants;
10031     while(*p && *p == *q) {
10032         if(*p == '\n') r = p+1, nPlayers++;
10033         p++; q++;
10034     }
10035     if(*p) { // difference
10036         while(*p && *p++ != '\n');
10037         while(*q && *q++ != '\n');
10038       changed = nPlayers;
10039         changes = 1 + (strcmp(p, q) != 0);
10040     }
10041     if(changes == 1) { // a single engine mnemonic was changed
10042         q = r; while(*q) nPlayers += (*q++ == '\n');
10043         p = buf; while(*r && (*p = *r++) != '\n') p++;
10044         *p = NULLCHAR;
10045         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10046         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10047         if(mnemonic[i]) { // The substitute is valid
10048             FILE *f;
10049             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10050                 flock(fileno(f), LOCK_EX);
10051                 ParseArgsFromFile(f);
10052                 fseek(f, 0, SEEK_SET);
10053                 FREE(appData.participants); appData.participants = participants;
10054                 if(expunge) { // erase results of replaced engine
10055                     int len = strlen(appData.results), w, b, dummy;
10056                     for(i=0; i<len; i++) {
10057                         Pairing(i, nPlayers, &w, &b, &dummy);
10058                         if((w == changed || b == changed) && appData.results[i] == '*') {
10059                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10060                             fclose(f);
10061                             return;
10062                         }
10063                     }
10064                     for(i=0; i<len; i++) {
10065                         Pairing(i, nPlayers, &w, &b, &dummy);
10066                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10067                     }
10068                 }
10069                 WriteTourneyFile(appData.results, f);
10070                 fclose(f); // release lock
10071                 return;
10072             }
10073         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10074     }
10075     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10076     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10077     free(participants);
10078     return;
10079 }
10080
10081 int
10082 CheckPlayers (char *participants)
10083 {
10084         int i;
10085         char buf[MSG_SIZ], *p;
10086         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10087         while(p = strchr(participants, '\n')) {
10088             *p = NULLCHAR;
10089             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10090             if(!mnemonic[i]) {
10091                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10092                 *p = '\n';
10093                 DisplayError(buf, 0);
10094                 return 1;
10095             }
10096             *p = '\n';
10097             participants = p + 1;
10098         }
10099         return 0;
10100 }
10101
10102 int
10103 CreateTourney (char *name)
10104 {
10105         FILE *f;
10106         if(matchMode && strcmp(name, appData.tourneyFile)) {
10107              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10108         }
10109         if(name[0] == NULLCHAR) {
10110             if(appData.participants[0])
10111                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10112             return 0;
10113         }
10114         f = fopen(name, "r");
10115         if(f) { // file exists
10116             ASSIGN(appData.tourneyFile, name);
10117             ParseArgsFromFile(f); // parse it
10118         } else {
10119             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10120             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10121                 DisplayError(_("Not enough participants"), 0);
10122                 return 0;
10123             }
10124             if(CheckPlayers(appData.participants)) return 0;
10125             ASSIGN(appData.tourneyFile, name);
10126             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10127             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10128         }
10129         fclose(f);
10130         appData.noChessProgram = FALSE;
10131         appData.clockMode = TRUE;
10132         SetGNUMode();
10133         return 1;
10134 }
10135
10136 int
10137 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10138 {
10139     char buf[MSG_SIZ], *p, *q;
10140     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10141     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10142     skip = !all && group[0]; // if group requested, we start in skip mode
10143     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10144         p = names; q = buf; header = 0;
10145         while(*p && *p != '\n') *q++ = *p++;
10146         *q = 0;
10147         if(*p == '\n') p++;
10148         if(buf[0] == '#') {
10149             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10150             depth++; // we must be entering a new group
10151             if(all) continue; // suppress printing group headers when complete list requested
10152             header = 1;
10153             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10154         }
10155         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10156         if(engineList[i]) free(engineList[i]);
10157         engineList[i] = strdup(buf);
10158         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10159         if(engineMnemonic[i]) free(engineMnemonic[i]);
10160         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10161             strcat(buf, " (");
10162             sscanf(q + 8, "%s", buf + strlen(buf));
10163             strcat(buf, ")");
10164         }
10165         engineMnemonic[i] = strdup(buf);
10166         i++;
10167     }
10168     engineList[i] = engineMnemonic[i] = NULL;
10169     return i;
10170 }
10171
10172 // following implemented as macro to avoid type limitations
10173 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10174
10175 void
10176 SwapEngines (int n)
10177 {   // swap settings for first engine and other engine (so far only some selected options)
10178     int h;
10179     char *p;
10180     if(n == 0) return;
10181     SWAP(directory, p)
10182     SWAP(chessProgram, p)
10183     SWAP(isUCI, h)
10184     SWAP(hasOwnBookUCI, h)
10185     SWAP(protocolVersion, h)
10186     SWAP(reuse, h)
10187     SWAP(scoreIsAbsolute, h)
10188     SWAP(timeOdds, h)
10189     SWAP(logo, p)
10190     SWAP(pgnName, p)
10191     SWAP(pvSAN, h)
10192     SWAP(engOptions, p)
10193     SWAP(engInitString, p)
10194     SWAP(computerString, p)
10195     SWAP(features, p)
10196     SWAP(fenOverride, p)
10197     SWAP(NPS, h)
10198     SWAP(accumulateTC, h)
10199     SWAP(host, p)
10200 }
10201
10202 int
10203 GetEngineLine (char *s, int n)
10204 {
10205     int i;
10206     char buf[MSG_SIZ];
10207     extern char *icsNames;
10208     if(!s || !*s) return 0;
10209     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10210     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10211     if(!mnemonic[i]) return 0;
10212     if(n == 11) return 1; // just testing if there was a match
10213     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10214     if(n == 1) SwapEngines(n);
10215     ParseArgsFromString(buf);
10216     if(n == 1) SwapEngines(n);
10217     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10218         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10219         ParseArgsFromString(buf);
10220     }
10221     return 1;
10222 }
10223
10224 int
10225 SetPlayer (int player, char *p)
10226 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10227     int i;
10228     char buf[MSG_SIZ], *engineName;
10229     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10230     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10231     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10232     if(mnemonic[i]) {
10233         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10234         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10235         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10236         ParseArgsFromString(buf);
10237     }
10238     free(engineName);
10239     return i;
10240 }
10241
10242 char *recentEngines;
10243
10244 void
10245 RecentEngineEvent (int nr)
10246 {
10247     int n;
10248 //    SwapEngines(1); // bump first to second
10249 //    ReplaceEngine(&second, 1); // and load it there
10250     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10251     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10252     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10253         ReplaceEngine(&first, 0);
10254         FloatToFront(&appData.recentEngineList, command[n]);
10255     }
10256 }
10257
10258 int
10259 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10260 {   // determine players from game number
10261     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10262
10263     if(appData.tourneyType == 0) {
10264         roundsPerCycle = (nPlayers - 1) | 1;
10265         pairingsPerRound = nPlayers / 2;
10266     } else if(appData.tourneyType > 0) {
10267         roundsPerCycle = nPlayers - appData.tourneyType;
10268         pairingsPerRound = appData.tourneyType;
10269     }
10270     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10271     gamesPerCycle = gamesPerRound * roundsPerCycle;
10272     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10273     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10274     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10275     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10276     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10277     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10278
10279     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10280     if(appData.roundSync) *syncInterval = gamesPerRound;
10281
10282     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10283
10284     if(appData.tourneyType == 0) {
10285         if(curPairing == (nPlayers-1)/2 ) {
10286             *whitePlayer = curRound;
10287             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10288         } else {
10289             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10290             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10291             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10292             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10293         }
10294     } else if(appData.tourneyType > 1) {
10295         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10296         *whitePlayer = curRound + appData.tourneyType;
10297     } else if(appData.tourneyType > 0) {
10298         *whitePlayer = curPairing;
10299         *blackPlayer = curRound + appData.tourneyType;
10300     }
10301
10302     // take care of white/black alternation per round. 
10303     // For cycles and games this is already taken care of by default, derived from matchGame!
10304     return curRound & 1;
10305 }
10306
10307 int
10308 NextTourneyGame (int nr, int *swapColors)
10309 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10310     char *p, *q;
10311     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10312     FILE *tf;
10313     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10314     tf = fopen(appData.tourneyFile, "r");
10315     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10316     ParseArgsFromFile(tf); fclose(tf);
10317     InitTimeControls(); // TC might be altered from tourney file
10318
10319     nPlayers = CountPlayers(appData.participants); // count participants
10320     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10321     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10322
10323     if(syncInterval) {
10324         p = q = appData.results;
10325         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10326         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10327             DisplayMessage(_("Waiting for other game(s)"),"");
10328             waitingForGame = TRUE;
10329             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10330             return 0;
10331         }
10332         waitingForGame = FALSE;
10333     }
10334
10335     if(appData.tourneyType < 0) {
10336         if(nr>=0 && !pairingReceived) {
10337             char buf[1<<16];
10338             if(pairing.pr == NoProc) {
10339                 if(!appData.pairingEngine[0]) {
10340                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10341                     return 0;
10342                 }
10343                 StartChessProgram(&pairing); // starts the pairing engine
10344             }
10345             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10346             SendToProgram(buf, &pairing);
10347             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10348             SendToProgram(buf, &pairing);
10349             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10350         }
10351         pairingReceived = 0;                              // ... so we continue here 
10352         *swapColors = 0;
10353         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10354         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10355         matchGame = 1; roundNr = nr / syncInterval + 1;
10356     }
10357
10358     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10359
10360     // redefine engines, engine dir, etc.
10361     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10362     if(first.pr == NoProc) {
10363       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10364       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10365     }
10366     if(second.pr == NoProc) {
10367       SwapEngines(1);
10368       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10369       SwapEngines(1);         // and make that valid for second engine by swapping
10370       InitEngine(&second, 1);
10371     }
10372     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10373     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10374     return 1;
10375 }
10376
10377 void
10378 NextMatchGame ()
10379 {   // performs game initialization that does not invoke engines, and then tries to start the game
10380     int res, firstWhite, swapColors = 0;
10381     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10382     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
10383         char buf[MSG_SIZ];
10384         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10385         if(strcmp(buf, currentDebugFile)) { // name has changed
10386             FILE *f = fopen(buf, "w");
10387             if(f) { // if opening the new file failed, just keep using the old one
10388                 ASSIGN(currentDebugFile, buf);
10389                 fclose(debugFP);
10390                 debugFP = f;
10391             }
10392             if(appData.serverFileName) {
10393                 if(serverFP) fclose(serverFP);
10394                 serverFP = fopen(appData.serverFileName, "w");
10395                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10396                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10397             }
10398         }
10399     }
10400     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10401     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10402     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10403     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10404     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10405     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10406     Reset(FALSE, first.pr != NoProc);
10407     res = LoadGameOrPosition(matchGame); // setup game
10408     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10409     if(!res) return; // abort when bad game/pos file
10410     TwoMachinesEvent();
10411 }
10412
10413 void
10414 UserAdjudicationEvent (int result)
10415 {
10416     ChessMove gameResult = GameIsDrawn;
10417
10418     if( result > 0 ) {
10419         gameResult = WhiteWins;
10420     }
10421     else if( result < 0 ) {
10422         gameResult = BlackWins;
10423     }
10424
10425     if( gameMode == TwoMachinesPlay ) {
10426         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10427     }
10428 }
10429
10430
10431 // [HGM] save: calculate checksum of game to make games easily identifiable
10432 int
10433 StringCheckSum (char *s)
10434 {
10435         int i = 0;
10436         if(s==NULL) return 0;
10437         while(*s) i = i*259 + *s++;
10438         return i;
10439 }
10440
10441 int
10442 GameCheckSum ()
10443 {
10444         int i, sum=0;
10445         for(i=backwardMostMove; i<forwardMostMove; i++) {
10446                 sum += pvInfoList[i].depth;
10447                 sum += StringCheckSum(parseList[i]);
10448                 sum += StringCheckSum(commentList[i]);
10449                 sum *= 261;
10450         }
10451         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10452         return sum + StringCheckSum(commentList[i]);
10453 } // end of save patch
10454
10455 void
10456 GameEnds (ChessMove result, char *resultDetails, int whosays)
10457 {
10458     GameMode nextGameMode;
10459     int isIcsGame;
10460     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10461
10462     if(endingGame) return; /* [HGM] crash: forbid recursion */
10463     endingGame = 1;
10464     if(twoBoards) { // [HGM] dual: switch back to one board
10465         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10466         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10467     }
10468     if (appData.debugMode) {
10469       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10470               result, resultDetails ? resultDetails : "(null)", whosays);
10471     }
10472
10473     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10474
10475     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10476         /* If we are playing on ICS, the server decides when the
10477            game is over, but the engine can offer to draw, claim
10478            a draw, or resign.
10479          */
10480 #if ZIPPY
10481         if (appData.zippyPlay && first.initDone) {
10482             if (result == GameIsDrawn) {
10483                 /* In case draw still needs to be claimed */
10484                 SendToICS(ics_prefix);
10485                 SendToICS("draw\n");
10486             } else if (StrCaseStr(resultDetails, "resign")) {
10487                 SendToICS(ics_prefix);
10488                 SendToICS("resign\n");
10489             }
10490         }
10491 #endif
10492         endingGame = 0; /* [HGM] crash */
10493         return;
10494     }
10495
10496     /* If we're loading the game from a file, stop */
10497     if (whosays == GE_FILE) {
10498       (void) StopLoadGameTimer();
10499       gameFileFP = NULL;
10500     }
10501
10502     /* Cancel draw offers */
10503     first.offeredDraw = second.offeredDraw = 0;
10504
10505     /* If this is an ICS game, only ICS can really say it's done;
10506        if not, anyone can. */
10507     isIcsGame = (gameMode == IcsPlayingWhite ||
10508                  gameMode == IcsPlayingBlack ||
10509                  gameMode == IcsObserving    ||
10510                  gameMode == IcsExamining);
10511
10512     if (!isIcsGame || whosays == GE_ICS) {
10513         /* OK -- not an ICS game, or ICS said it was done */
10514         StopClocks();
10515         if (!isIcsGame && !appData.noChessProgram)
10516           SetUserThinkingEnables();
10517
10518         /* [HGM] if a machine claims the game end we verify this claim */
10519         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10520             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10521                 char claimer;
10522                 ChessMove trueResult = (ChessMove) -1;
10523
10524                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10525                                             first.twoMachinesColor[0] :
10526                                             second.twoMachinesColor[0] ;
10527
10528                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10529                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10530                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10531                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10532                 } else
10533                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10534                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10535                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10536                 } else
10537                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10538                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10539                 }
10540
10541                 // now verify win claims, but not in drop games, as we don't understand those yet
10542                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10543                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10544                     (result == WhiteWins && claimer == 'w' ||
10545                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10546                       if (appData.debugMode) {
10547                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10548                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10549                       }
10550                       if(result != trueResult) {
10551                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10552                               result = claimer == 'w' ? BlackWins : WhiteWins;
10553                               resultDetails = buf;
10554                       }
10555                 } else
10556                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10557                     && (forwardMostMove <= backwardMostMove ||
10558                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10559                         (claimer=='b')==(forwardMostMove&1))
10560                                                                                   ) {
10561                       /* [HGM] verify: draws that were not flagged are false claims */
10562                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10563                       result = claimer == 'w' ? BlackWins : WhiteWins;
10564                       resultDetails = buf;
10565                 }
10566                 /* (Claiming a loss is accepted no questions asked!) */
10567             }
10568             /* [HGM] bare: don't allow bare King to win */
10569             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10570                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10571                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10572                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10573                && result != GameIsDrawn)
10574             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10575                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10576                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10577                         if(p >= 0 && p <= (int)WhiteKing) k++;
10578                 }
10579                 if (appData.debugMode) {
10580                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10581                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10582                 }
10583                 if(k <= 1) {
10584                         result = GameIsDrawn;
10585                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10586                         resultDetails = buf;
10587                 }
10588             }
10589         }
10590
10591
10592         if(serverMoves != NULL && !loadFlag) { char c = '=';
10593             if(result==WhiteWins) c = '+';
10594             if(result==BlackWins) c = '-';
10595             if(resultDetails != NULL)
10596                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10597         }
10598         if (resultDetails != NULL) {
10599             gameInfo.result = result;
10600             gameInfo.resultDetails = StrSave(resultDetails);
10601
10602             /* display last move only if game was not loaded from file */
10603             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10604                 DisplayMove(currentMove - 1);
10605
10606             if (forwardMostMove != 0) {
10607                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10608                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10609                                                                 ) {
10610                     if (*appData.saveGameFile != NULLCHAR) {
10611                         SaveGameToFile(appData.saveGameFile, TRUE);
10612                     } else if (appData.autoSaveGames) {
10613                         AutoSaveGame();
10614                     }
10615                     if (*appData.savePositionFile != NULLCHAR) {
10616                         SavePositionToFile(appData.savePositionFile);
10617                     }
10618                 }
10619             }
10620
10621             /* Tell program how game ended in case it is learning */
10622             /* [HGM] Moved this to after saving the PGN, just in case */
10623             /* engine died and we got here through time loss. In that */
10624             /* case we will get a fatal error writing the pipe, which */
10625             /* would otherwise lose us the PGN.                       */
10626             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10627             /* output during GameEnds should never be fatal anymore   */
10628             if (gameMode == MachinePlaysWhite ||
10629                 gameMode == MachinePlaysBlack ||
10630                 gameMode == TwoMachinesPlay ||
10631                 gameMode == IcsPlayingWhite ||
10632                 gameMode == IcsPlayingBlack ||
10633                 gameMode == BeginningOfGame) {
10634                 char buf[MSG_SIZ];
10635                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10636                         resultDetails);
10637                 if (first.pr != NoProc) {
10638                     SendToProgram(buf, &first);
10639                 }
10640                 if (second.pr != NoProc &&
10641                     gameMode == TwoMachinesPlay) {
10642                     SendToProgram(buf, &second);
10643                 }
10644             }
10645         }
10646
10647         if (appData.icsActive) {
10648             if (appData.quietPlay &&
10649                 (gameMode == IcsPlayingWhite ||
10650                  gameMode == IcsPlayingBlack)) {
10651                 SendToICS(ics_prefix);
10652                 SendToICS("set shout 1\n");
10653             }
10654             nextGameMode = IcsIdle;
10655             ics_user_moved = FALSE;
10656             /* clean up premove.  It's ugly when the game has ended and the
10657              * premove highlights are still on the board.
10658              */
10659             if (gotPremove) {
10660               gotPremove = FALSE;
10661               ClearPremoveHighlights();
10662               DrawPosition(FALSE, boards[currentMove]);
10663             }
10664             if (whosays == GE_ICS) {
10665                 switch (result) {
10666                 case WhiteWins:
10667                     if (gameMode == IcsPlayingWhite)
10668                         PlayIcsWinSound();
10669                     else if(gameMode == IcsPlayingBlack)
10670                         PlayIcsLossSound();
10671                     break;
10672                 case BlackWins:
10673                     if (gameMode == IcsPlayingBlack)
10674                         PlayIcsWinSound();
10675                     else if(gameMode == IcsPlayingWhite)
10676                         PlayIcsLossSound();
10677                     break;
10678                 case GameIsDrawn:
10679                     PlayIcsDrawSound();
10680                     break;
10681                 default:
10682                     PlayIcsUnfinishedSound();
10683                 }
10684             }
10685         } else if (gameMode == EditGame ||
10686                    gameMode == PlayFromGameFile ||
10687                    gameMode == AnalyzeMode ||
10688                    gameMode == AnalyzeFile) {
10689             nextGameMode = gameMode;
10690         } else {
10691             nextGameMode = EndOfGame;
10692         }
10693         pausing = FALSE;
10694         ModeHighlight();
10695     } else {
10696         nextGameMode = gameMode;
10697     }
10698
10699     if (appData.noChessProgram) {
10700         gameMode = nextGameMode;
10701         ModeHighlight();
10702         endingGame = 0; /* [HGM] crash */
10703         return;
10704     }
10705
10706     if (first.reuse) {
10707         /* Put first chess program into idle state */
10708         if (first.pr != NoProc &&
10709             (gameMode == MachinePlaysWhite ||
10710              gameMode == MachinePlaysBlack ||
10711              gameMode == TwoMachinesPlay ||
10712              gameMode == IcsPlayingWhite ||
10713              gameMode == IcsPlayingBlack ||
10714              gameMode == BeginningOfGame)) {
10715             SendToProgram("force\n", &first);
10716             if (first.usePing) {
10717               char buf[MSG_SIZ];
10718               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10719               SendToProgram(buf, &first);
10720             }
10721         }
10722     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10723         /* Kill off first chess program */
10724         if (first.isr != NULL)
10725           RemoveInputSource(first.isr);
10726         first.isr = NULL;
10727
10728         if (first.pr != NoProc) {
10729             ExitAnalyzeMode();
10730             DoSleep( appData.delayBeforeQuit );
10731             SendToProgram("quit\n", &first);
10732             DoSleep( appData.delayAfterQuit );
10733             DestroyChildProcess(first.pr, first.useSigterm);
10734         }
10735         first.pr = NoProc;
10736     }
10737     if (second.reuse) {
10738         /* Put second chess program into idle state */
10739         if (second.pr != NoProc &&
10740             gameMode == TwoMachinesPlay) {
10741             SendToProgram("force\n", &second);
10742             if (second.usePing) {
10743               char buf[MSG_SIZ];
10744               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10745               SendToProgram(buf, &second);
10746             }
10747         }
10748     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10749         /* Kill off second chess program */
10750         if (second.isr != NULL)
10751           RemoveInputSource(second.isr);
10752         second.isr = NULL;
10753
10754         if (second.pr != NoProc) {
10755             DoSleep( appData.delayBeforeQuit );
10756             SendToProgram("quit\n", &second);
10757             DoSleep( appData.delayAfterQuit );
10758             DestroyChildProcess(second.pr, second.useSigterm);
10759         }
10760         second.pr = NoProc;
10761     }
10762
10763     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10764         char resChar = '=';
10765         switch (result) {
10766         case WhiteWins:
10767           resChar = '+';
10768           if (first.twoMachinesColor[0] == 'w') {
10769             first.matchWins++;
10770           } else {
10771             second.matchWins++;
10772           }
10773           break;
10774         case BlackWins:
10775           resChar = '-';
10776           if (first.twoMachinesColor[0] == 'b') {
10777             first.matchWins++;
10778           } else {
10779             second.matchWins++;
10780           }
10781           break;
10782         case GameUnfinished:
10783           resChar = ' ';
10784         default:
10785           break;
10786         }
10787
10788         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10789         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10790             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10791             ReserveGame(nextGame, resChar); // sets nextGame
10792             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10793             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10794         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10795
10796         if (nextGame <= appData.matchGames && !abortMatch) {
10797             gameMode = nextGameMode;
10798             matchGame = nextGame; // this will be overruled in tourney mode!
10799             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10800             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10801             endingGame = 0; /* [HGM] crash */
10802             return;
10803         } else {
10804             gameMode = nextGameMode;
10805             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10806                      first.tidy, second.tidy,
10807                      first.matchWins, second.matchWins,
10808                      appData.matchGames - (first.matchWins + second.matchWins));
10809             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10810             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10811             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10812             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10813                 first.twoMachinesColor = "black\n";
10814                 second.twoMachinesColor = "white\n";
10815             } else {
10816                 first.twoMachinesColor = "white\n";
10817                 second.twoMachinesColor = "black\n";
10818             }
10819         }
10820     }
10821     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10822         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10823       ExitAnalyzeMode();
10824     gameMode = nextGameMode;
10825     ModeHighlight();
10826     endingGame = 0;  /* [HGM] crash */
10827     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10828         if(matchMode == TRUE) { // match through command line: exit with or without popup
10829             if(ranking) {
10830                 ToNrEvent(forwardMostMove);
10831                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10832                 else ExitEvent(0);
10833             } else DisplayFatalError(buf, 0, 0);
10834         } else { // match through menu; just stop, with or without popup
10835             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10836             ModeHighlight();
10837             if(ranking){
10838                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10839             } else DisplayNote(buf);
10840       }
10841       if(ranking) free(ranking);
10842     }
10843 }
10844
10845 /* Assumes program was just initialized (initString sent).
10846    Leaves program in force mode. */
10847 void
10848 FeedMovesToProgram (ChessProgramState *cps, int upto)
10849 {
10850     int i;
10851
10852     if (appData.debugMode)
10853       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10854               startedFromSetupPosition ? "position and " : "",
10855               backwardMostMove, upto, cps->which);
10856     if(currentlyInitializedVariant != gameInfo.variant) {
10857       char buf[MSG_SIZ];
10858         // [HGM] variantswitch: make engine aware of new variant
10859         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10860                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10861         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10862         SendToProgram(buf, cps);
10863         currentlyInitializedVariant = gameInfo.variant;
10864     }
10865     SendToProgram("force\n", cps);
10866     if (startedFromSetupPosition) {
10867         SendBoard(cps, backwardMostMove);
10868     if (appData.debugMode) {
10869         fprintf(debugFP, "feedMoves\n");
10870     }
10871     }
10872     for (i = backwardMostMove; i < upto; i++) {
10873         SendMoveToProgram(i, cps);
10874     }
10875 }
10876
10877
10878 int
10879 ResurrectChessProgram ()
10880 {
10881      /* The chess program may have exited.
10882         If so, restart it and feed it all the moves made so far. */
10883     static int doInit = 0;
10884
10885     if (appData.noChessProgram) return 1;
10886
10887     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10888         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10889         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10890         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10891     } else {
10892         if (first.pr != NoProc) return 1;
10893         StartChessProgram(&first);
10894     }
10895     InitChessProgram(&first, FALSE);
10896     FeedMovesToProgram(&first, currentMove);
10897
10898     if (!first.sendTime) {
10899         /* can't tell gnuchess what its clock should read,
10900            so we bow to its notion. */
10901         ResetClocks();
10902         timeRemaining[0][currentMove] = whiteTimeRemaining;
10903         timeRemaining[1][currentMove] = blackTimeRemaining;
10904     }
10905
10906     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10907                 appData.icsEngineAnalyze) && first.analysisSupport) {
10908       SendToProgram("analyze\n", &first);
10909       first.analyzing = TRUE;
10910     }
10911     return 1;
10912 }
10913
10914 /*
10915  * Button procedures
10916  */
10917 void
10918 Reset (int redraw, int init)
10919 {
10920     int i;
10921
10922     if (appData.debugMode) {
10923         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10924                 redraw, init, gameMode);
10925     }
10926     CleanupTail(); // [HGM] vari: delete any stored variations
10927     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10928     pausing = pauseExamInvalid = FALSE;
10929     startedFromSetupPosition = blackPlaysFirst = FALSE;
10930     firstMove = TRUE;
10931     whiteFlag = blackFlag = FALSE;
10932     userOfferedDraw = FALSE;
10933     hintRequested = bookRequested = FALSE;
10934     first.maybeThinking = FALSE;
10935     second.maybeThinking = FALSE;
10936     first.bookSuspend = FALSE; // [HGM] book
10937     second.bookSuspend = FALSE;
10938     thinkOutput[0] = NULLCHAR;
10939     lastHint[0] = NULLCHAR;
10940     ClearGameInfo(&gameInfo);
10941     gameInfo.variant = StringToVariant(appData.variant);
10942     ics_user_moved = ics_clock_paused = FALSE;
10943     ics_getting_history = H_FALSE;
10944     ics_gamenum = -1;
10945     white_holding[0] = black_holding[0] = NULLCHAR;
10946     ClearProgramStats();
10947     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10948
10949     ResetFrontEnd();
10950     ClearHighlights();
10951     flipView = appData.flipView;
10952     ClearPremoveHighlights();
10953     gotPremove = FALSE;
10954     alarmSounded = FALSE;
10955
10956     GameEnds(EndOfFile, NULL, GE_PLAYER);
10957     if(appData.serverMovesName != NULL) {
10958         /* [HGM] prepare to make moves file for broadcasting */
10959         clock_t t = clock();
10960         if(serverMoves != NULL) fclose(serverMoves);
10961         serverMoves = fopen(appData.serverMovesName, "r");
10962         if(serverMoves != NULL) {
10963             fclose(serverMoves);
10964             /* delay 15 sec before overwriting, so all clients can see end */
10965             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10966         }
10967         serverMoves = fopen(appData.serverMovesName, "w");
10968     }
10969
10970     ExitAnalyzeMode();
10971     gameMode = BeginningOfGame;
10972     ModeHighlight();
10973     if(appData.icsActive) gameInfo.variant = VariantNormal;
10974     currentMove = forwardMostMove = backwardMostMove = 0;
10975     MarkTargetSquares(1);
10976     InitPosition(redraw);
10977     for (i = 0; i < MAX_MOVES; i++) {
10978         if (commentList[i] != NULL) {
10979             free(commentList[i]);
10980             commentList[i] = NULL;
10981         }
10982     }
10983     ResetClocks();
10984     timeRemaining[0][0] = whiteTimeRemaining;
10985     timeRemaining[1][0] = blackTimeRemaining;
10986
10987     if (first.pr == NoProc) {
10988         StartChessProgram(&first);
10989     }
10990     if (init) {
10991             InitChessProgram(&first, startedFromSetupPosition);
10992     }
10993     DisplayTitle("");
10994     DisplayMessage("", "");
10995     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10996     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10997     ClearMap();        // [HGM] exclude: invalidate map
10998 }
10999
11000 void
11001 AutoPlayGameLoop ()
11002 {
11003     for (;;) {
11004         if (!AutoPlayOneMove())
11005           return;
11006         if (matchMode || appData.timeDelay == 0)
11007           continue;
11008         if (appData.timeDelay < 0)
11009           return;
11010         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11011         break;
11012     }
11013 }
11014
11015 void
11016 AnalyzeNextGame()
11017 {
11018     ReloadGame(1); // next game
11019 }
11020
11021 int
11022 AutoPlayOneMove ()
11023 {
11024     int fromX, fromY, toX, toY;
11025
11026     if (appData.debugMode) {
11027       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11028     }
11029
11030     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11031       return FALSE;
11032
11033     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11034       pvInfoList[currentMove].depth = programStats.depth;
11035       pvInfoList[currentMove].score = programStats.score;
11036       pvInfoList[currentMove].time  = 0;
11037       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11038     }
11039
11040     if (currentMove >= forwardMostMove) {
11041       if(gameMode == AnalyzeFile) {
11042           if(appData.loadGameIndex == -1) {
11043             GameEnds(EndOfFile, NULL, GE_FILE);
11044           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11045           } else {
11046           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11047         }
11048       }
11049 //      gameMode = EndOfGame;
11050 //      ModeHighlight();
11051
11052       /* [AS] Clear current move marker at the end of a game */
11053       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11054
11055       return FALSE;
11056     }
11057
11058     toX = moveList[currentMove][2] - AAA;
11059     toY = moveList[currentMove][3] - ONE;
11060
11061     if (moveList[currentMove][1] == '@') {
11062         if (appData.highlightLastMove) {
11063             SetHighlights(-1, -1, toX, toY);
11064         }
11065     } else {
11066         fromX = moveList[currentMove][0] - AAA;
11067         fromY = moveList[currentMove][1] - ONE;
11068
11069         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11070
11071         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11072
11073         if (appData.highlightLastMove) {
11074             SetHighlights(fromX, fromY, toX, toY);
11075         }
11076     }
11077     DisplayMove(currentMove);
11078     SendMoveToProgram(currentMove++, &first);
11079     DisplayBothClocks();
11080     DrawPosition(FALSE, boards[currentMove]);
11081     // [HGM] PV info: always display, routine tests if empty
11082     DisplayComment(currentMove - 1, commentList[currentMove]);
11083     return TRUE;
11084 }
11085
11086
11087 int
11088 LoadGameOneMove (ChessMove readAhead)
11089 {
11090     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11091     char promoChar = NULLCHAR;
11092     ChessMove moveType;
11093     char move[MSG_SIZ];
11094     char *p, *q;
11095
11096     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11097         gameMode != AnalyzeMode && gameMode != Training) {
11098         gameFileFP = NULL;
11099         return FALSE;
11100     }
11101
11102     yyboardindex = forwardMostMove;
11103     if (readAhead != EndOfFile) {
11104       moveType = readAhead;
11105     } else {
11106       if (gameFileFP == NULL)
11107           return FALSE;
11108       moveType = (ChessMove) Myylex();
11109     }
11110
11111     done = FALSE;
11112     switch (moveType) {
11113       case Comment:
11114         if (appData.debugMode)
11115           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11116         p = yy_text;
11117
11118         /* append the comment but don't display it */
11119         AppendComment(currentMove, p, FALSE);
11120         return TRUE;
11121
11122       case WhiteCapturesEnPassant:
11123       case BlackCapturesEnPassant:
11124       case WhitePromotion:
11125       case BlackPromotion:
11126       case WhiteNonPromotion:
11127       case BlackNonPromotion:
11128       case NormalMove:
11129       case WhiteKingSideCastle:
11130       case WhiteQueenSideCastle:
11131       case BlackKingSideCastle:
11132       case BlackQueenSideCastle:
11133       case WhiteKingSideCastleWild:
11134       case WhiteQueenSideCastleWild:
11135       case BlackKingSideCastleWild:
11136       case BlackQueenSideCastleWild:
11137       /* PUSH Fabien */
11138       case WhiteHSideCastleFR:
11139       case WhiteASideCastleFR:
11140       case BlackHSideCastleFR:
11141       case BlackASideCastleFR:
11142       /* POP Fabien */
11143         if (appData.debugMode)
11144           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11145         fromX = currentMoveString[0] - AAA;
11146         fromY = currentMoveString[1] - ONE;
11147         toX = currentMoveString[2] - AAA;
11148         toY = currentMoveString[3] - ONE;
11149         promoChar = currentMoveString[4];
11150         break;
11151
11152       case WhiteDrop:
11153       case BlackDrop:
11154         if (appData.debugMode)
11155           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11156         fromX = moveType == WhiteDrop ?
11157           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11158         (int) CharToPiece(ToLower(currentMoveString[0]));
11159         fromY = DROP_RANK;
11160         toX = currentMoveString[2] - AAA;
11161         toY = currentMoveString[3] - ONE;
11162         break;
11163
11164       case WhiteWins:
11165       case BlackWins:
11166       case GameIsDrawn:
11167       case GameUnfinished:
11168         if (appData.debugMode)
11169           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11170         p = strchr(yy_text, '{');
11171         if (p == NULL) p = strchr(yy_text, '(');
11172         if (p == NULL) {
11173             p = yy_text;
11174             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11175         } else {
11176             q = strchr(p, *p == '{' ? '}' : ')');
11177             if (q != NULL) *q = NULLCHAR;
11178             p++;
11179         }
11180         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11181         GameEnds(moveType, p, GE_FILE);
11182         done = TRUE;
11183         if (cmailMsgLoaded) {
11184             ClearHighlights();
11185             flipView = WhiteOnMove(currentMove);
11186             if (moveType == GameUnfinished) flipView = !flipView;
11187             if (appData.debugMode)
11188               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11189         }
11190         break;
11191
11192       case EndOfFile:
11193         if (appData.debugMode)
11194           fprintf(debugFP, "Parser hit end of file\n");
11195         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11196           case MT_NONE:
11197           case MT_CHECK:
11198             break;
11199           case MT_CHECKMATE:
11200           case MT_STAINMATE:
11201             if (WhiteOnMove(currentMove)) {
11202                 GameEnds(BlackWins, "Black mates", GE_FILE);
11203             } else {
11204                 GameEnds(WhiteWins, "White mates", GE_FILE);
11205             }
11206             break;
11207           case MT_STALEMATE:
11208             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11209             break;
11210         }
11211         done = TRUE;
11212         break;
11213
11214       case MoveNumberOne:
11215         if (lastLoadGameStart == GNUChessGame) {
11216             /* GNUChessGames have numbers, but they aren't move numbers */
11217             if (appData.debugMode)
11218               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11219                       yy_text, (int) moveType);
11220             return LoadGameOneMove(EndOfFile); /* tail recursion */
11221         }
11222         /* else fall thru */
11223
11224       case XBoardGame:
11225       case GNUChessGame:
11226       case PGNTag:
11227         /* Reached start of next game in file */
11228         if (appData.debugMode)
11229           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11230         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11231           case MT_NONE:
11232           case MT_CHECK:
11233             break;
11234           case MT_CHECKMATE:
11235           case MT_STAINMATE:
11236             if (WhiteOnMove(currentMove)) {
11237                 GameEnds(BlackWins, "Black mates", GE_FILE);
11238             } else {
11239                 GameEnds(WhiteWins, "White mates", GE_FILE);
11240             }
11241             break;
11242           case MT_STALEMATE:
11243             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11244             break;
11245         }
11246         done = TRUE;
11247         break;
11248
11249       case PositionDiagram:     /* should not happen; ignore */
11250       case ElapsedTime:         /* ignore */
11251       case NAG:                 /* ignore */
11252         if (appData.debugMode)
11253           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11254                   yy_text, (int) moveType);
11255         return LoadGameOneMove(EndOfFile); /* tail recursion */
11256
11257       case IllegalMove:
11258         if (appData.testLegality) {
11259             if (appData.debugMode)
11260               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11261             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11262                     (forwardMostMove / 2) + 1,
11263                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11264             DisplayError(move, 0);
11265             done = TRUE;
11266         } else {
11267             if (appData.debugMode)
11268               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11269                       yy_text, currentMoveString);
11270             fromX = currentMoveString[0] - AAA;
11271             fromY = currentMoveString[1] - ONE;
11272             toX = currentMoveString[2] - AAA;
11273             toY = currentMoveString[3] - ONE;
11274             promoChar = currentMoveString[4];
11275         }
11276         break;
11277
11278       case AmbiguousMove:
11279         if (appData.debugMode)
11280           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11281         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11282                 (forwardMostMove / 2) + 1,
11283                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11284         DisplayError(move, 0);
11285         done = TRUE;
11286         break;
11287
11288       default:
11289       case ImpossibleMove:
11290         if (appData.debugMode)
11291           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11292         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11293                 (forwardMostMove / 2) + 1,
11294                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11295         DisplayError(move, 0);
11296         done = TRUE;
11297         break;
11298     }
11299
11300     if (done) {
11301         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11302             DrawPosition(FALSE, boards[currentMove]);
11303             DisplayBothClocks();
11304             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11305               DisplayComment(currentMove - 1, commentList[currentMove]);
11306         }
11307         (void) StopLoadGameTimer();
11308         gameFileFP = NULL;
11309         cmailOldMove = forwardMostMove;
11310         return FALSE;
11311     } else {
11312         /* currentMoveString is set as a side-effect of yylex */
11313
11314         thinkOutput[0] = NULLCHAR;
11315         MakeMove(fromX, fromY, toX, toY, promoChar);
11316         currentMove = forwardMostMove;
11317         return TRUE;
11318     }
11319 }
11320
11321 /* Load the nth game from the given file */
11322 int
11323 LoadGameFromFile (char *filename, int n, char *title, int useList)
11324 {
11325     FILE *f;
11326     char buf[MSG_SIZ];
11327
11328     if (strcmp(filename, "-") == 0) {
11329         f = stdin;
11330         title = "stdin";
11331     } else {
11332         f = fopen(filename, "rb");
11333         if (f == NULL) {
11334           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11335             DisplayError(buf, errno);
11336             return FALSE;
11337         }
11338     }
11339     if (fseek(f, 0, 0) == -1) {
11340         /* f is not seekable; probably a pipe */
11341         useList = FALSE;
11342     }
11343     if (useList && n == 0) {
11344         int error = GameListBuild(f);
11345         if (error) {
11346             DisplayError(_("Cannot build game list"), error);
11347         } else if (!ListEmpty(&gameList) &&
11348                    ((ListGame *) gameList.tailPred)->number > 1) {
11349             GameListPopUp(f, title);
11350             return TRUE;
11351         }
11352         GameListDestroy();
11353         n = 1;
11354     }
11355     if (n == 0) n = 1;
11356     return LoadGame(f, n, title, FALSE);
11357 }
11358
11359
11360 void
11361 MakeRegisteredMove ()
11362 {
11363     int fromX, fromY, toX, toY;
11364     char promoChar;
11365     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11366         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11367           case CMAIL_MOVE:
11368           case CMAIL_DRAW:
11369             if (appData.debugMode)
11370               fprintf(debugFP, "Restoring %s for game %d\n",
11371                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11372
11373             thinkOutput[0] = NULLCHAR;
11374             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11375             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11376             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11377             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11378             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11379             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11380             MakeMove(fromX, fromY, toX, toY, promoChar);
11381             ShowMove(fromX, fromY, toX, toY);
11382
11383             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11384               case MT_NONE:
11385               case MT_CHECK:
11386                 break;
11387
11388               case MT_CHECKMATE:
11389               case MT_STAINMATE:
11390                 if (WhiteOnMove(currentMove)) {
11391                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11392                 } else {
11393                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11394                 }
11395                 break;
11396
11397               case MT_STALEMATE:
11398                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11399                 break;
11400             }
11401
11402             break;
11403
11404           case CMAIL_RESIGN:
11405             if (WhiteOnMove(currentMove)) {
11406                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11407             } else {
11408                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11409             }
11410             break;
11411
11412           case CMAIL_ACCEPT:
11413             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11414             break;
11415
11416           default:
11417             break;
11418         }
11419     }
11420
11421     return;
11422 }
11423
11424 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11425 int
11426 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11427 {
11428     int retVal;
11429
11430     if (gameNumber > nCmailGames) {
11431         DisplayError(_("No more games in this message"), 0);
11432         return FALSE;
11433     }
11434     if (f == lastLoadGameFP) {
11435         int offset = gameNumber - lastLoadGameNumber;
11436         if (offset == 0) {
11437             cmailMsg[0] = NULLCHAR;
11438             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11439                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11440                 nCmailMovesRegistered--;
11441             }
11442             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11443             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11444                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11445             }
11446         } else {
11447             if (! RegisterMove()) return FALSE;
11448         }
11449     }
11450
11451     retVal = LoadGame(f, gameNumber, title, useList);
11452
11453     /* Make move registered during previous look at this game, if any */
11454     MakeRegisteredMove();
11455
11456     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11457         commentList[currentMove]
11458           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11459         DisplayComment(currentMove - 1, commentList[currentMove]);
11460     }
11461
11462     return retVal;
11463 }
11464
11465 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11466 int
11467 ReloadGame (int offset)
11468 {
11469     int gameNumber = lastLoadGameNumber + offset;
11470     if (lastLoadGameFP == NULL) {
11471         DisplayError(_("No game has been loaded yet"), 0);
11472         return FALSE;
11473     }
11474     if (gameNumber <= 0) {
11475         DisplayError(_("Can't back up any further"), 0);
11476         return FALSE;
11477     }
11478     if (cmailMsgLoaded) {
11479         return CmailLoadGame(lastLoadGameFP, gameNumber,
11480                              lastLoadGameTitle, lastLoadGameUseList);
11481     } else {
11482         return LoadGame(lastLoadGameFP, gameNumber,
11483                         lastLoadGameTitle, lastLoadGameUseList);
11484     }
11485 }
11486
11487 int keys[EmptySquare+1];
11488
11489 int
11490 PositionMatches (Board b1, Board b2)
11491 {
11492     int r, f, sum=0;
11493     switch(appData.searchMode) {
11494         case 1: return CompareWithRights(b1, b2);
11495         case 2:
11496             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11497                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11498             }
11499             return TRUE;
11500         case 3:
11501             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11502               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11503                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11504             }
11505             return sum==0;
11506         case 4:
11507             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11508                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11509             }
11510             return sum==0;
11511     }
11512     return TRUE;
11513 }
11514
11515 #define Q_PROMO  4
11516 #define Q_EP     3
11517 #define Q_BCASTL 2
11518 #define Q_WCASTL 1
11519
11520 int pieceList[256], quickBoard[256];
11521 ChessSquare pieceType[256] = { EmptySquare };
11522 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11523 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11524 int soughtTotal, turn;
11525 Boolean epOK, flipSearch;
11526
11527 typedef struct {
11528     unsigned char piece, to;
11529 } Move;
11530
11531 #define DSIZE (250000)
11532
11533 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11534 Move *moveDatabase = initialSpace;
11535 unsigned int movePtr, dataSize = DSIZE;
11536
11537 int
11538 MakePieceList (Board board, int *counts)
11539 {
11540     int r, f, n=Q_PROMO, total=0;
11541     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11542     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11543         int sq = f + (r<<4);
11544         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11545             quickBoard[sq] = ++n;
11546             pieceList[n] = sq;
11547             pieceType[n] = board[r][f];
11548             counts[board[r][f]]++;
11549             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11550             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11551             total++;
11552         }
11553     }
11554     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11555     return total;
11556 }
11557
11558 void
11559 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11560 {
11561     int sq = fromX + (fromY<<4);
11562     int piece = quickBoard[sq];
11563     quickBoard[sq] = 0;
11564     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11565     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11566         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11567         moveDatabase[movePtr++].piece = Q_WCASTL;
11568         quickBoard[sq] = piece;
11569         piece = quickBoard[from]; quickBoard[from] = 0;
11570         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11571     } else
11572     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11573         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11574         moveDatabase[movePtr++].piece = Q_BCASTL;
11575         quickBoard[sq] = piece;
11576         piece = quickBoard[from]; quickBoard[from] = 0;
11577         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11578     } else
11579     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11580         quickBoard[(fromY<<4)+toX] = 0;
11581         moveDatabase[movePtr].piece = Q_EP;
11582         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11583         moveDatabase[movePtr].to = sq;
11584     } else
11585     if(promoPiece != pieceType[piece]) {
11586         moveDatabase[movePtr++].piece = Q_PROMO;
11587         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11588     }
11589     moveDatabase[movePtr].piece = piece;
11590     quickBoard[sq] = piece;
11591     movePtr++;
11592 }
11593
11594 int
11595 PackGame (Board board)
11596 {
11597     Move *newSpace = NULL;
11598     moveDatabase[movePtr].piece = 0; // terminate previous game
11599     if(movePtr > dataSize) {
11600         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11601         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11602         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11603         if(newSpace) {
11604             int i;
11605             Move *p = moveDatabase, *q = newSpace;
11606             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11607             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11608             moveDatabase = newSpace;
11609         } else { // calloc failed, we must be out of memory. Too bad...
11610             dataSize = 0; // prevent calloc events for all subsequent games
11611             return 0;     // and signal this one isn't cached
11612         }
11613     }
11614     movePtr++;
11615     MakePieceList(board, counts);
11616     return movePtr;
11617 }
11618
11619 int
11620 QuickCompare (Board board, int *minCounts, int *maxCounts)
11621 {   // compare according to search mode
11622     int r, f;
11623     switch(appData.searchMode)
11624     {
11625       case 1: // exact position match
11626         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11627         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11628             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11629         }
11630         break;
11631       case 2: // can have extra material on empty squares
11632         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11633             if(board[r][f] == EmptySquare) continue;
11634             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11635         }
11636         break;
11637       case 3: // material with exact Pawn structure
11638         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11639             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11640             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11641         } // fall through to material comparison
11642       case 4: // exact material
11643         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11644         break;
11645       case 6: // material range with given imbalance
11646         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11647         // fall through to range comparison
11648       case 5: // material range
11649         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11650     }
11651     return TRUE;
11652 }
11653
11654 int
11655 QuickScan (Board board, Move *move)
11656 {   // reconstruct game,and compare all positions in it
11657     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11658     do {
11659         int piece = move->piece;
11660         int to = move->to, from = pieceList[piece];
11661         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11662           if(!piece) return -1;
11663           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11664             piece = (++move)->piece;
11665             from = pieceList[piece];
11666             counts[pieceType[piece]]--;
11667             pieceType[piece] = (ChessSquare) move->to;
11668             counts[move->to]++;
11669           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11670             counts[pieceType[quickBoard[to]]]--;
11671             quickBoard[to] = 0; total--;
11672             move++;
11673             continue;
11674           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11675             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11676             from  = pieceList[piece]; // so this must be King
11677             quickBoard[from] = 0;
11678             pieceList[piece] = to;
11679             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11680             quickBoard[from] = 0; // rook
11681             quickBoard[to] = piece;
11682             to = move->to; piece = move->piece;
11683             goto aftercastle;
11684           }
11685         }
11686         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11687         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11688         quickBoard[from] = 0;
11689       aftercastle:
11690         quickBoard[to] = piece;
11691         pieceList[piece] = to;
11692         cnt++; turn ^= 3;
11693         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11694            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11695            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11696                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11697           ) {
11698             static int lastCounts[EmptySquare+1];
11699             int i;
11700             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11701             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11702         } else stretch = 0;
11703         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11704         move++;
11705     } while(1);
11706 }
11707
11708 void
11709 InitSearch ()
11710 {
11711     int r, f;
11712     flipSearch = FALSE;
11713     CopyBoard(soughtBoard, boards[currentMove]);
11714     soughtTotal = MakePieceList(soughtBoard, maxSought);
11715     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11716     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11717     CopyBoard(reverseBoard, boards[currentMove]);
11718     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11719         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11720         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11721         reverseBoard[r][f] = piece;
11722     }
11723     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11724     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11725     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11726                  || (boards[currentMove][CASTLING][2] == NoRights || 
11727                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11728                  && (boards[currentMove][CASTLING][5] == NoRights || 
11729                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11730       ) {
11731         flipSearch = TRUE;
11732         CopyBoard(flipBoard, soughtBoard);
11733         CopyBoard(rotateBoard, reverseBoard);
11734         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11735             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11736             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11737         }
11738     }
11739     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11740     if(appData.searchMode >= 5) {
11741         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11742         MakePieceList(soughtBoard, minSought);
11743         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11744     }
11745     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11746         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11747 }
11748
11749 GameInfo dummyInfo;
11750
11751 int
11752 GameContainsPosition (FILE *f, ListGame *lg)
11753 {
11754     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11755     int fromX, fromY, toX, toY;
11756     char promoChar;
11757     static int initDone=FALSE;
11758
11759     // weed out games based on numerical tag comparison
11760     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11761     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11762     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11763     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11764     if(!initDone) {
11765         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11766         initDone = TRUE;
11767     }
11768     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11769     else CopyBoard(boards[scratch], initialPosition); // default start position
11770     if(lg->moves) {
11771         turn = btm + 1;
11772         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11773         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11774     }
11775     if(btm) plyNr++;
11776     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11777     fseek(f, lg->offset, 0);
11778     yynewfile(f);
11779     while(1) {
11780         yyboardindex = scratch;
11781         quickFlag = plyNr+1;
11782         next = Myylex();
11783         quickFlag = 0;
11784         switch(next) {
11785             case PGNTag:
11786                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11787             default:
11788                 continue;
11789
11790             case XBoardGame:
11791             case GNUChessGame:
11792                 if(plyNr) return -1; // after we have seen moves, this is for new game
11793               continue;
11794
11795             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11796             case ImpossibleMove:
11797             case WhiteWins: // game ends here with these four
11798             case BlackWins:
11799             case GameIsDrawn:
11800             case GameUnfinished:
11801                 return -1;
11802
11803             case IllegalMove:
11804                 if(appData.testLegality) return -1;
11805             case WhiteCapturesEnPassant:
11806             case BlackCapturesEnPassant:
11807             case WhitePromotion:
11808             case BlackPromotion:
11809             case WhiteNonPromotion:
11810             case BlackNonPromotion:
11811             case NormalMove:
11812             case WhiteKingSideCastle:
11813             case WhiteQueenSideCastle:
11814             case BlackKingSideCastle:
11815             case BlackQueenSideCastle:
11816             case WhiteKingSideCastleWild:
11817             case WhiteQueenSideCastleWild:
11818             case BlackKingSideCastleWild:
11819             case BlackQueenSideCastleWild:
11820             case WhiteHSideCastleFR:
11821             case WhiteASideCastleFR:
11822             case BlackHSideCastleFR:
11823             case BlackASideCastleFR:
11824                 fromX = currentMoveString[0] - AAA;
11825                 fromY = currentMoveString[1] - ONE;
11826                 toX = currentMoveString[2] - AAA;
11827                 toY = currentMoveString[3] - ONE;
11828                 promoChar = currentMoveString[4];
11829                 break;
11830             case WhiteDrop:
11831             case BlackDrop:
11832                 fromX = next == WhiteDrop ?
11833                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11834                   (int) CharToPiece(ToLower(currentMoveString[0]));
11835                 fromY = DROP_RANK;
11836                 toX = currentMoveString[2] - AAA;
11837                 toY = currentMoveString[3] - ONE;
11838                 promoChar = 0;
11839                 break;
11840         }
11841         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11842         plyNr++;
11843         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11844         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11845         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11846         if(appData.findMirror) {
11847             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11848             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11849         }
11850     }
11851 }
11852
11853 /* Load the nth game from open file f */
11854 int
11855 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11856 {
11857     ChessMove cm;
11858     char buf[MSG_SIZ];
11859     int gn = gameNumber;
11860     ListGame *lg = NULL;
11861     int numPGNTags = 0;
11862     int err, pos = -1;
11863     GameMode oldGameMode;
11864     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11865
11866     if (appData.debugMode)
11867         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11868
11869     if (gameMode == Training )
11870         SetTrainingModeOff();
11871
11872     oldGameMode = gameMode;
11873     if (gameMode != BeginningOfGame) {
11874       Reset(FALSE, TRUE);
11875     }
11876
11877     gameFileFP = f;
11878     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11879         fclose(lastLoadGameFP);
11880     }
11881
11882     if (useList) {
11883         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11884
11885         if (lg) {
11886             fseek(f, lg->offset, 0);
11887             GameListHighlight(gameNumber);
11888             pos = lg->position;
11889             gn = 1;
11890         }
11891         else {
11892             if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
11893               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
11894             else
11895             DisplayError(_("Game number out of range"), 0);
11896             return FALSE;
11897         }
11898     } else {
11899         GameListDestroy();
11900         if (fseek(f, 0, 0) == -1) {
11901             if (f == lastLoadGameFP ?
11902                 gameNumber == lastLoadGameNumber + 1 :
11903                 gameNumber == 1) {
11904                 gn = 1;
11905             } else {
11906                 DisplayError(_("Can't seek on game file"), 0);
11907                 return FALSE;
11908             }
11909         }
11910     }
11911     lastLoadGameFP = f;
11912     lastLoadGameNumber = gameNumber;
11913     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11914     lastLoadGameUseList = useList;
11915
11916     yynewfile(f);
11917
11918     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11919       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11920                 lg->gameInfo.black);
11921             DisplayTitle(buf);
11922     } else if (*title != NULLCHAR) {
11923         if (gameNumber > 1) {
11924           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11925             DisplayTitle(buf);
11926         } else {
11927             DisplayTitle(title);
11928         }
11929     }
11930
11931     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11932         gameMode = PlayFromGameFile;
11933         ModeHighlight();
11934     }
11935
11936     currentMove = forwardMostMove = backwardMostMove = 0;
11937     CopyBoard(boards[0], initialPosition);
11938     StopClocks();
11939
11940     /*
11941      * Skip the first gn-1 games in the file.
11942      * Also skip over anything that precedes an identifiable
11943      * start of game marker, to avoid being confused by
11944      * garbage at the start of the file.  Currently
11945      * recognized start of game markers are the move number "1",
11946      * the pattern "gnuchess .* game", the pattern
11947      * "^[#;%] [^ ]* game file", and a PGN tag block.
11948      * A game that starts with one of the latter two patterns
11949      * will also have a move number 1, possibly
11950      * following a position diagram.
11951      * 5-4-02: Let's try being more lenient and allowing a game to
11952      * start with an unnumbered move.  Does that break anything?
11953      */
11954     cm = lastLoadGameStart = EndOfFile;
11955     while (gn > 0) {
11956         yyboardindex = forwardMostMove;
11957         cm = (ChessMove) Myylex();
11958         switch (cm) {
11959           case EndOfFile:
11960             if (cmailMsgLoaded) {
11961                 nCmailGames = CMAIL_MAX_GAMES - gn;
11962             } else {
11963                 Reset(TRUE, TRUE);
11964                 DisplayError(_("Game not found in file"), 0);
11965             }
11966             return FALSE;
11967
11968           case GNUChessGame:
11969           case XBoardGame:
11970             gn--;
11971             lastLoadGameStart = cm;
11972             break;
11973
11974           case MoveNumberOne:
11975             switch (lastLoadGameStart) {
11976               case GNUChessGame:
11977               case XBoardGame:
11978               case PGNTag:
11979                 break;
11980               case MoveNumberOne:
11981               case EndOfFile:
11982                 gn--;           /* count this game */
11983                 lastLoadGameStart = cm;
11984                 break;
11985               default:
11986                 /* impossible */
11987                 break;
11988             }
11989             break;
11990
11991           case PGNTag:
11992             switch (lastLoadGameStart) {
11993               case GNUChessGame:
11994               case PGNTag:
11995               case MoveNumberOne:
11996               case EndOfFile:
11997                 gn--;           /* count this game */
11998                 lastLoadGameStart = cm;
11999                 break;
12000               case XBoardGame:
12001                 lastLoadGameStart = cm; /* game counted already */
12002                 break;
12003               default:
12004                 /* impossible */
12005                 break;
12006             }
12007             if (gn > 0) {
12008                 do {
12009                     yyboardindex = forwardMostMove;
12010                     cm = (ChessMove) Myylex();
12011                 } while (cm == PGNTag || cm == Comment);
12012             }
12013             break;
12014
12015           case WhiteWins:
12016           case BlackWins:
12017           case GameIsDrawn:
12018             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12019                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12020                     != CMAIL_OLD_RESULT) {
12021                     nCmailResults ++ ;
12022                     cmailResult[  CMAIL_MAX_GAMES
12023                                 - gn - 1] = CMAIL_OLD_RESULT;
12024                 }
12025             }
12026             break;
12027
12028           case NormalMove:
12029             /* Only a NormalMove can be at the start of a game
12030              * without a position diagram. */
12031             if (lastLoadGameStart == EndOfFile ) {
12032               gn--;
12033               lastLoadGameStart = MoveNumberOne;
12034             }
12035             break;
12036
12037           default:
12038             break;
12039         }
12040     }
12041
12042     if (appData.debugMode)
12043       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12044
12045     if (cm == XBoardGame) {
12046         /* Skip any header junk before position diagram and/or move 1 */
12047         for (;;) {
12048             yyboardindex = forwardMostMove;
12049             cm = (ChessMove) Myylex();
12050
12051             if (cm == EndOfFile ||
12052                 cm == GNUChessGame || cm == XBoardGame) {
12053                 /* Empty game; pretend end-of-file and handle later */
12054                 cm = EndOfFile;
12055                 break;
12056             }
12057
12058             if (cm == MoveNumberOne || cm == PositionDiagram ||
12059                 cm == PGNTag || cm == Comment)
12060               break;
12061         }
12062     } else if (cm == GNUChessGame) {
12063         if (gameInfo.event != NULL) {
12064             free(gameInfo.event);
12065         }
12066         gameInfo.event = StrSave(yy_text);
12067     }
12068
12069     startedFromSetupPosition = FALSE;
12070     while (cm == PGNTag) {
12071         if (appData.debugMode)
12072           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12073         err = ParsePGNTag(yy_text, &gameInfo);
12074         if (!err) numPGNTags++;
12075
12076         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12077         if(gameInfo.variant != oldVariant) {
12078             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12079             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12080             InitPosition(TRUE);
12081             oldVariant = gameInfo.variant;
12082             if (appData.debugMode)
12083               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12084         }
12085
12086
12087         if (gameInfo.fen != NULL) {
12088           Board initial_position;
12089           startedFromSetupPosition = TRUE;
12090           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12091             Reset(TRUE, TRUE);
12092             DisplayError(_("Bad FEN position in file"), 0);
12093             return FALSE;
12094           }
12095           CopyBoard(boards[0], initial_position);
12096           if (blackPlaysFirst) {
12097             currentMove = forwardMostMove = backwardMostMove = 1;
12098             CopyBoard(boards[1], initial_position);
12099             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12100             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12101             timeRemaining[0][1] = whiteTimeRemaining;
12102             timeRemaining[1][1] = blackTimeRemaining;
12103             if (commentList[0] != NULL) {
12104               commentList[1] = commentList[0];
12105               commentList[0] = NULL;
12106             }
12107           } else {
12108             currentMove = forwardMostMove = backwardMostMove = 0;
12109           }
12110           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12111           {   int i;
12112               initialRulePlies = FENrulePlies;
12113               for( i=0; i< nrCastlingRights; i++ )
12114                   initialRights[i] = initial_position[CASTLING][i];
12115           }
12116           yyboardindex = forwardMostMove;
12117           free(gameInfo.fen);
12118           gameInfo.fen = NULL;
12119         }
12120
12121         yyboardindex = forwardMostMove;
12122         cm = (ChessMove) Myylex();
12123
12124         /* Handle comments interspersed among the tags */
12125         while (cm == Comment) {
12126             char *p;
12127             if (appData.debugMode)
12128               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12129             p = yy_text;
12130             AppendComment(currentMove, p, FALSE);
12131             yyboardindex = forwardMostMove;
12132             cm = (ChessMove) Myylex();
12133         }
12134     }
12135
12136     /* don't rely on existence of Event tag since if game was
12137      * pasted from clipboard the Event tag may not exist
12138      */
12139     if (numPGNTags > 0){
12140         char *tags;
12141         if (gameInfo.variant == VariantNormal) {
12142           VariantClass v = StringToVariant(gameInfo.event);
12143           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12144           if(v < VariantShogi) gameInfo.variant = v;
12145         }
12146         if (!matchMode) {
12147           if( appData.autoDisplayTags ) {
12148             tags = PGNTags(&gameInfo);
12149             TagsPopUp(tags, CmailMsg());
12150             free(tags);
12151           }
12152         }
12153     } else {
12154         /* Make something up, but don't display it now */
12155         SetGameInfo();
12156         TagsPopDown();
12157     }
12158
12159     if (cm == PositionDiagram) {
12160         int i, j;
12161         char *p;
12162         Board initial_position;
12163
12164         if (appData.debugMode)
12165           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12166
12167         if (!startedFromSetupPosition) {
12168             p = yy_text;
12169             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12170               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12171                 switch (*p) {
12172                   case '{':
12173                   case '[':
12174                   case '-':
12175                   case ' ':
12176                   case '\t':
12177                   case '\n':
12178                   case '\r':
12179                     break;
12180                   default:
12181                     initial_position[i][j++] = CharToPiece(*p);
12182                     break;
12183                 }
12184             while (*p == ' ' || *p == '\t' ||
12185                    *p == '\n' || *p == '\r') p++;
12186
12187             if (strncmp(p, "black", strlen("black"))==0)
12188               blackPlaysFirst = TRUE;
12189             else
12190               blackPlaysFirst = FALSE;
12191             startedFromSetupPosition = TRUE;
12192
12193             CopyBoard(boards[0], initial_position);
12194             if (blackPlaysFirst) {
12195                 currentMove = forwardMostMove = backwardMostMove = 1;
12196                 CopyBoard(boards[1], initial_position);
12197                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12198                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12199                 timeRemaining[0][1] = whiteTimeRemaining;
12200                 timeRemaining[1][1] = blackTimeRemaining;
12201                 if (commentList[0] != NULL) {
12202                     commentList[1] = commentList[0];
12203                     commentList[0] = NULL;
12204                 }
12205             } else {
12206                 currentMove = forwardMostMove = backwardMostMove = 0;
12207             }
12208         }
12209         yyboardindex = forwardMostMove;
12210         cm = (ChessMove) Myylex();
12211     }
12212
12213     if (first.pr == NoProc) {
12214         StartChessProgram(&first);
12215     }
12216     InitChessProgram(&first, FALSE);
12217     SendToProgram("force\n", &first);
12218     if (startedFromSetupPosition) {
12219         SendBoard(&first, forwardMostMove);
12220     if (appData.debugMode) {
12221         fprintf(debugFP, "Load Game\n");
12222     }
12223         DisplayBothClocks();
12224     }
12225
12226     /* [HGM] server: flag to write setup moves in broadcast file as one */
12227     loadFlag = appData.suppressLoadMoves;
12228
12229     while (cm == Comment) {
12230         char *p;
12231         if (appData.debugMode)
12232           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12233         p = yy_text;
12234         AppendComment(currentMove, p, FALSE);
12235         yyboardindex = forwardMostMove;
12236         cm = (ChessMove) Myylex();
12237     }
12238
12239     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12240         cm == WhiteWins || cm == BlackWins ||
12241         cm == GameIsDrawn || cm == GameUnfinished) {
12242         DisplayMessage("", _("No moves in game"));
12243         if (cmailMsgLoaded) {
12244             if (appData.debugMode)
12245               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12246             ClearHighlights();
12247             flipView = FALSE;
12248         }
12249         DrawPosition(FALSE, boards[currentMove]);
12250         DisplayBothClocks();
12251         gameMode = EditGame;
12252         ModeHighlight();
12253         gameFileFP = NULL;
12254         cmailOldMove = 0;
12255         return TRUE;
12256     }
12257
12258     // [HGM] PV info: routine tests if comment empty
12259     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12260         DisplayComment(currentMove - 1, commentList[currentMove]);
12261     }
12262     if (!matchMode && appData.timeDelay != 0)
12263       DrawPosition(FALSE, boards[currentMove]);
12264
12265     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12266       programStats.ok_to_send = 1;
12267     }
12268
12269     /* if the first token after the PGN tags is a move
12270      * and not move number 1, retrieve it from the parser
12271      */
12272     if (cm != MoveNumberOne)
12273         LoadGameOneMove(cm);
12274
12275     /* load the remaining moves from the file */
12276     while (LoadGameOneMove(EndOfFile)) {
12277       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12278       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12279     }
12280
12281     /* rewind to the start of the game */
12282     currentMove = backwardMostMove;
12283
12284     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12285
12286     if (oldGameMode == AnalyzeFile ||
12287         oldGameMode == AnalyzeMode) {
12288       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12289       keepInfo = 1;
12290       AnalyzeFileEvent();
12291       keepInfo = 0;
12292     }
12293
12294     if (!matchMode && pos > 0) {
12295         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12296     } else
12297     if (matchMode || appData.timeDelay == 0) {
12298       ToEndEvent();
12299     } else if (appData.timeDelay > 0) {
12300       AutoPlayGameLoop();
12301     }
12302
12303     if (appData.debugMode)
12304         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12305
12306     loadFlag = 0; /* [HGM] true game starts */
12307     return TRUE;
12308 }
12309
12310 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12311 int
12312 ReloadPosition (int offset)
12313 {
12314     int positionNumber = lastLoadPositionNumber + offset;
12315     if (lastLoadPositionFP == NULL) {
12316         DisplayError(_("No position has been loaded yet"), 0);
12317         return FALSE;
12318     }
12319     if (positionNumber <= 0) {
12320         DisplayError(_("Can't back up any further"), 0);
12321         return FALSE;
12322     }
12323     return LoadPosition(lastLoadPositionFP, positionNumber,
12324                         lastLoadPositionTitle);
12325 }
12326
12327 /* Load the nth position from the given file */
12328 int
12329 LoadPositionFromFile (char *filename, int n, char *title)
12330 {
12331     FILE *f;
12332     char buf[MSG_SIZ];
12333
12334     if (strcmp(filename, "-") == 0) {
12335         return LoadPosition(stdin, n, "stdin");
12336     } else {
12337         f = fopen(filename, "rb");
12338         if (f == NULL) {
12339             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12340             DisplayError(buf, errno);
12341             return FALSE;
12342         } else {
12343             return LoadPosition(f, n, title);
12344         }
12345     }
12346 }
12347
12348 /* Load the nth position from the given open file, and close it */
12349 int
12350 LoadPosition (FILE *f, int positionNumber, char *title)
12351 {
12352     char *p, line[MSG_SIZ];
12353     Board initial_position;
12354     int i, j, fenMode, pn;
12355
12356     if (gameMode == Training )
12357         SetTrainingModeOff();
12358
12359     if (gameMode != BeginningOfGame) {
12360         Reset(FALSE, TRUE);
12361     }
12362     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12363         fclose(lastLoadPositionFP);
12364     }
12365     if (positionNumber == 0) positionNumber = 1;
12366     lastLoadPositionFP = f;
12367     lastLoadPositionNumber = positionNumber;
12368     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12369     if (first.pr == NoProc && !appData.noChessProgram) {
12370       StartChessProgram(&first);
12371       InitChessProgram(&first, FALSE);
12372     }
12373     pn = positionNumber;
12374     if (positionNumber < 0) {
12375         /* Negative position number means to seek to that byte offset */
12376         if (fseek(f, -positionNumber, 0) == -1) {
12377             DisplayError(_("Can't seek on position file"), 0);
12378             return FALSE;
12379         };
12380         pn = 1;
12381     } else {
12382         if (fseek(f, 0, 0) == -1) {
12383             if (f == lastLoadPositionFP ?
12384                 positionNumber == lastLoadPositionNumber + 1 :
12385                 positionNumber == 1) {
12386                 pn = 1;
12387             } else {
12388                 DisplayError(_("Can't seek on position file"), 0);
12389                 return FALSE;
12390             }
12391         }
12392     }
12393     /* See if this file is FEN or old-style xboard */
12394     if (fgets(line, MSG_SIZ, f) == NULL) {
12395         DisplayError(_("Position not found in file"), 0);
12396         return FALSE;
12397     }
12398     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12399     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12400
12401     if (pn >= 2) {
12402         if (fenMode || line[0] == '#') pn--;
12403         while (pn > 0) {
12404             /* skip positions before number pn */
12405             if (fgets(line, MSG_SIZ, f) == NULL) {
12406                 Reset(TRUE, TRUE);
12407                 DisplayError(_("Position not found in file"), 0);
12408                 return FALSE;
12409             }
12410             if (fenMode || line[0] == '#') pn--;
12411         }
12412     }
12413
12414     if (fenMode) {
12415         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12416             DisplayError(_("Bad FEN position in file"), 0);
12417             return FALSE;
12418         }
12419     } else {
12420         (void) fgets(line, MSG_SIZ, f);
12421         (void) fgets(line, MSG_SIZ, f);
12422
12423         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12424             (void) fgets(line, MSG_SIZ, f);
12425             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12426                 if (*p == ' ')
12427                   continue;
12428                 initial_position[i][j++] = CharToPiece(*p);
12429             }
12430         }
12431
12432         blackPlaysFirst = FALSE;
12433         if (!feof(f)) {
12434             (void) fgets(line, MSG_SIZ, f);
12435             if (strncmp(line, "black", strlen("black"))==0)
12436               blackPlaysFirst = TRUE;
12437         }
12438     }
12439     startedFromSetupPosition = TRUE;
12440
12441     CopyBoard(boards[0], initial_position);
12442     if (blackPlaysFirst) {
12443         currentMove = forwardMostMove = backwardMostMove = 1;
12444         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12445         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12446         CopyBoard(boards[1], initial_position);
12447         DisplayMessage("", _("Black to play"));
12448     } else {
12449         currentMove = forwardMostMove = backwardMostMove = 0;
12450         DisplayMessage("", _("White to play"));
12451     }
12452     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12453     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12454         SendToProgram("force\n", &first);
12455         SendBoard(&first, forwardMostMove);
12456     }
12457     if (appData.debugMode) {
12458 int i, j;
12459   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12460   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12461         fprintf(debugFP, "Load Position\n");
12462     }
12463
12464     if (positionNumber > 1) {
12465       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12466         DisplayTitle(line);
12467     } else {
12468         DisplayTitle(title);
12469     }
12470     gameMode = EditGame;
12471     ModeHighlight();
12472     ResetClocks();
12473     timeRemaining[0][1] = whiteTimeRemaining;
12474     timeRemaining[1][1] = blackTimeRemaining;
12475     DrawPosition(FALSE, boards[currentMove]);
12476
12477     return TRUE;
12478 }
12479
12480
12481 void
12482 CopyPlayerNameIntoFileName (char **dest, char *src)
12483 {
12484     while (*src != NULLCHAR && *src != ',') {
12485         if (*src == ' ') {
12486             *(*dest)++ = '_';
12487             src++;
12488         } else {
12489             *(*dest)++ = *src++;
12490         }
12491     }
12492 }
12493
12494 char *
12495 DefaultFileName (char *ext)
12496 {
12497     static char def[MSG_SIZ];
12498     char *p;
12499
12500     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12501         p = def;
12502         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12503         *p++ = '-';
12504         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12505         *p++ = '.';
12506         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12507     } else {
12508         def[0] = NULLCHAR;
12509     }
12510     return def;
12511 }
12512
12513 /* Save the current game to the given file */
12514 int
12515 SaveGameToFile (char *filename, int append)
12516 {
12517     FILE *f;
12518     char buf[MSG_SIZ];
12519     int result, i, t,tot=0;
12520
12521     if (strcmp(filename, "-") == 0) {
12522         return SaveGame(stdout, 0, NULL);
12523     } else {
12524         for(i=0; i<10; i++) { // upto 10 tries
12525              f = fopen(filename, append ? "a" : "w");
12526              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12527              if(f || errno != 13) break;
12528              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12529              tot += t;
12530         }
12531         if (f == NULL) {
12532             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12533             DisplayError(buf, errno);
12534             return FALSE;
12535         } else {
12536             safeStrCpy(buf, lastMsg, MSG_SIZ);
12537             DisplayMessage(_("Waiting for access to save file"), "");
12538             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12539             DisplayMessage(_("Saving game"), "");
12540             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12541             result = SaveGame(f, 0, NULL);
12542             DisplayMessage(buf, "");
12543             return result;
12544         }
12545     }
12546 }
12547
12548 char *
12549 SavePart (char *str)
12550 {
12551     static char buf[MSG_SIZ];
12552     char *p;
12553
12554     p = strchr(str, ' ');
12555     if (p == NULL) return str;
12556     strncpy(buf, str, p - str);
12557     buf[p - str] = NULLCHAR;
12558     return buf;
12559 }
12560
12561 #define PGN_MAX_LINE 75
12562
12563 #define PGN_SIDE_WHITE  0
12564 #define PGN_SIDE_BLACK  1
12565
12566 static int
12567 FindFirstMoveOutOfBook (int side)
12568 {
12569     int result = -1;
12570
12571     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12572         int index = backwardMostMove;
12573         int has_book_hit = 0;
12574
12575         if( (index % 2) != side ) {
12576             index++;
12577         }
12578
12579         while( index < forwardMostMove ) {
12580             /* Check to see if engine is in book */
12581             int depth = pvInfoList[index].depth;
12582             int score = pvInfoList[index].score;
12583             int in_book = 0;
12584
12585             if( depth <= 2 ) {
12586                 in_book = 1;
12587             }
12588             else if( score == 0 && depth == 63 ) {
12589                 in_book = 1; /* Zappa */
12590             }
12591             else if( score == 2 && depth == 99 ) {
12592                 in_book = 1; /* Abrok */
12593             }
12594
12595             has_book_hit += in_book;
12596
12597             if( ! in_book ) {
12598                 result = index;
12599
12600                 break;
12601             }
12602
12603             index += 2;
12604         }
12605     }
12606
12607     return result;
12608 }
12609
12610 void
12611 GetOutOfBookInfo (char * buf)
12612 {
12613     int oob[2];
12614     int i;
12615     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12616
12617     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12618     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12619
12620     *buf = '\0';
12621
12622     if( oob[0] >= 0 || oob[1] >= 0 ) {
12623         for( i=0; i<2; i++ ) {
12624             int idx = oob[i];
12625
12626             if( idx >= 0 ) {
12627                 if( i > 0 && oob[0] >= 0 ) {
12628                     strcat( buf, "   " );
12629                 }
12630
12631                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12632                 sprintf( buf+strlen(buf), "%s%.2f",
12633                     pvInfoList[idx].score >= 0 ? "+" : "",
12634                     pvInfoList[idx].score / 100.0 );
12635             }
12636         }
12637     }
12638 }
12639
12640 /* Save game in PGN style and close the file */
12641 int
12642 SaveGamePGN (FILE *f)
12643 {
12644     int i, offset, linelen, newblock;
12645 //    char *movetext;
12646     char numtext[32];
12647     int movelen, numlen, blank;
12648     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12649
12650     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12651
12652     PrintPGNTags(f, &gameInfo);
12653
12654     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12655
12656     if (backwardMostMove > 0 || startedFromSetupPosition) {
12657         char *fen = PositionToFEN(backwardMostMove, NULL);
12658         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12659         fprintf(f, "\n{--------------\n");
12660         PrintPosition(f, backwardMostMove);
12661         fprintf(f, "--------------}\n");
12662         free(fen);
12663     }
12664     else {
12665         /* [AS] Out of book annotation */
12666         if( appData.saveOutOfBookInfo ) {
12667             char buf[64];
12668
12669             GetOutOfBookInfo( buf );
12670
12671             if( buf[0] != '\0' ) {
12672                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12673             }
12674         }
12675
12676         fprintf(f, "\n");
12677     }
12678
12679     i = backwardMostMove;
12680     linelen = 0;
12681     newblock = TRUE;
12682
12683     while (i < forwardMostMove) {
12684         /* Print comments preceding this move */
12685         if (commentList[i] != NULL) {
12686             if (linelen > 0) fprintf(f, "\n");
12687             fprintf(f, "%s", commentList[i]);
12688             linelen = 0;
12689             newblock = TRUE;
12690         }
12691
12692         /* Format move number */
12693         if ((i % 2) == 0)
12694           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12695         else
12696           if (newblock)
12697             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12698           else
12699             numtext[0] = NULLCHAR;
12700
12701         numlen = strlen(numtext);
12702         newblock = FALSE;
12703
12704         /* Print move number */
12705         blank = linelen > 0 && numlen > 0;
12706         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12707             fprintf(f, "\n");
12708             linelen = 0;
12709             blank = 0;
12710         }
12711         if (blank) {
12712             fprintf(f, " ");
12713             linelen++;
12714         }
12715         fprintf(f, "%s", numtext);
12716         linelen += numlen;
12717
12718         /* Get move */
12719         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12720         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12721
12722         /* Print move */
12723         blank = linelen > 0 && movelen > 0;
12724         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12725             fprintf(f, "\n");
12726             linelen = 0;
12727             blank = 0;
12728         }
12729         if (blank) {
12730             fprintf(f, " ");
12731             linelen++;
12732         }
12733         fprintf(f, "%s", move_buffer);
12734         linelen += movelen;
12735
12736         /* [AS] Add PV info if present */
12737         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12738             /* [HGM] add time */
12739             char buf[MSG_SIZ]; int seconds;
12740
12741             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12742
12743             if( seconds <= 0)
12744               buf[0] = 0;
12745             else
12746               if( seconds < 30 )
12747                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12748               else
12749                 {
12750                   seconds = (seconds + 4)/10; // round to full seconds
12751                   if( seconds < 60 )
12752                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12753                   else
12754                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12755                 }
12756
12757             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12758                       pvInfoList[i].score >= 0 ? "+" : "",
12759                       pvInfoList[i].score / 100.0,
12760                       pvInfoList[i].depth,
12761                       buf );
12762
12763             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12764
12765             /* Print score/depth */
12766             blank = linelen > 0 && movelen > 0;
12767             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12768                 fprintf(f, "\n");
12769                 linelen = 0;
12770                 blank = 0;
12771             }
12772             if (blank) {
12773                 fprintf(f, " ");
12774                 linelen++;
12775             }
12776             fprintf(f, "%s", move_buffer);
12777             linelen += movelen;
12778         }
12779
12780         i++;
12781     }
12782
12783     /* Start a new line */
12784     if (linelen > 0) fprintf(f, "\n");
12785
12786     /* Print comments after last move */
12787     if (commentList[i] != NULL) {
12788         fprintf(f, "%s\n", commentList[i]);
12789     }
12790
12791     /* Print result */
12792     if (gameInfo.resultDetails != NULL &&
12793         gameInfo.resultDetails[0] != NULLCHAR) {
12794         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12795                 PGNResult(gameInfo.result));
12796     } else {
12797         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12798     }
12799
12800     fclose(f);
12801     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12802     return TRUE;
12803 }
12804
12805 /* Save game in old style and close the file */
12806 int
12807 SaveGameOldStyle (FILE *f)
12808 {
12809     int i, offset;
12810     time_t tm;
12811
12812     tm = time((time_t *) NULL);
12813
12814     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12815     PrintOpponents(f);
12816
12817     if (backwardMostMove > 0 || startedFromSetupPosition) {
12818         fprintf(f, "\n[--------------\n");
12819         PrintPosition(f, backwardMostMove);
12820         fprintf(f, "--------------]\n");
12821     } else {
12822         fprintf(f, "\n");
12823     }
12824
12825     i = backwardMostMove;
12826     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12827
12828     while (i < forwardMostMove) {
12829         if (commentList[i] != NULL) {
12830             fprintf(f, "[%s]\n", commentList[i]);
12831         }
12832
12833         if ((i % 2) == 1) {
12834             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12835             i++;
12836         } else {
12837             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12838             i++;
12839             if (commentList[i] != NULL) {
12840                 fprintf(f, "\n");
12841                 continue;
12842             }
12843             if (i >= forwardMostMove) {
12844                 fprintf(f, "\n");
12845                 break;
12846             }
12847             fprintf(f, "%s\n", parseList[i]);
12848             i++;
12849         }
12850     }
12851
12852     if (commentList[i] != NULL) {
12853         fprintf(f, "[%s]\n", commentList[i]);
12854     }
12855
12856     /* This isn't really the old style, but it's close enough */
12857     if (gameInfo.resultDetails != NULL &&
12858         gameInfo.resultDetails[0] != NULLCHAR) {
12859         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12860                 gameInfo.resultDetails);
12861     } else {
12862         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12863     }
12864
12865     fclose(f);
12866     return TRUE;
12867 }
12868
12869 /* Save the current game to open file f and close the file */
12870 int
12871 SaveGame (FILE *f, int dummy, char *dummy2)
12872 {
12873     if (gameMode == EditPosition) EditPositionDone(TRUE);
12874     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12875     if (appData.oldSaveStyle)
12876       return SaveGameOldStyle(f);
12877     else
12878       return SaveGamePGN(f);
12879 }
12880
12881 /* Save the current position to the given file */
12882 int
12883 SavePositionToFile (char *filename)
12884 {
12885     FILE *f;
12886     char buf[MSG_SIZ];
12887
12888     if (strcmp(filename, "-") == 0) {
12889         return SavePosition(stdout, 0, NULL);
12890     } else {
12891         f = fopen(filename, "a");
12892         if (f == NULL) {
12893             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12894             DisplayError(buf, errno);
12895             return FALSE;
12896         } else {
12897             safeStrCpy(buf, lastMsg, MSG_SIZ);
12898             DisplayMessage(_("Waiting for access to save file"), "");
12899             flock(fileno(f), LOCK_EX); // [HGM] lock
12900             DisplayMessage(_("Saving position"), "");
12901             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12902             SavePosition(f, 0, NULL);
12903             DisplayMessage(buf, "");
12904             return TRUE;
12905         }
12906     }
12907 }
12908
12909 /* Save the current position to the given open file and close the file */
12910 int
12911 SavePosition (FILE *f, int dummy, char *dummy2)
12912 {
12913     time_t tm;
12914     char *fen;
12915
12916     if (gameMode == EditPosition) EditPositionDone(TRUE);
12917     if (appData.oldSaveStyle) {
12918         tm = time((time_t *) NULL);
12919
12920         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12921         PrintOpponents(f);
12922         fprintf(f, "[--------------\n");
12923         PrintPosition(f, currentMove);
12924         fprintf(f, "--------------]\n");
12925     } else {
12926         fen = PositionToFEN(currentMove, NULL);
12927         fprintf(f, "%s\n", fen);
12928         free(fen);
12929     }
12930     fclose(f);
12931     return TRUE;
12932 }
12933
12934 void
12935 ReloadCmailMsgEvent (int unregister)
12936 {
12937 #if !WIN32
12938     static char *inFilename = NULL;
12939     static char *outFilename;
12940     int i;
12941     struct stat inbuf, outbuf;
12942     int status;
12943
12944     /* Any registered moves are unregistered if unregister is set, */
12945     /* i.e. invoked by the signal handler */
12946     if (unregister) {
12947         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12948             cmailMoveRegistered[i] = FALSE;
12949             if (cmailCommentList[i] != NULL) {
12950                 free(cmailCommentList[i]);
12951                 cmailCommentList[i] = NULL;
12952             }
12953         }
12954         nCmailMovesRegistered = 0;
12955     }
12956
12957     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12958         cmailResult[i] = CMAIL_NOT_RESULT;
12959     }
12960     nCmailResults = 0;
12961
12962     if (inFilename == NULL) {
12963         /* Because the filenames are static they only get malloced once  */
12964         /* and they never get freed                                      */
12965         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12966         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12967
12968         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12969         sprintf(outFilename, "%s.out", appData.cmailGameName);
12970     }
12971
12972     status = stat(outFilename, &outbuf);
12973     if (status < 0) {
12974         cmailMailedMove = FALSE;
12975     } else {
12976         status = stat(inFilename, &inbuf);
12977         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12978     }
12979
12980     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12981        counts the games, notes how each one terminated, etc.
12982
12983        It would be nice to remove this kludge and instead gather all
12984        the information while building the game list.  (And to keep it
12985        in the game list nodes instead of having a bunch of fixed-size
12986        parallel arrays.)  Note this will require getting each game's
12987        termination from the PGN tags, as the game list builder does
12988        not process the game moves.  --mann
12989        */
12990     cmailMsgLoaded = TRUE;
12991     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12992
12993     /* Load first game in the file or popup game menu */
12994     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12995
12996 #endif /* !WIN32 */
12997     return;
12998 }
12999
13000 int
13001 RegisterMove ()
13002 {
13003     FILE *f;
13004     char string[MSG_SIZ];
13005
13006     if (   cmailMailedMove
13007         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13008         return TRUE;            /* Allow free viewing  */
13009     }
13010
13011     /* Unregister move to ensure that we don't leave RegisterMove        */
13012     /* with the move registered when the conditions for registering no   */
13013     /* longer hold                                                       */
13014     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13015         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13016         nCmailMovesRegistered --;
13017
13018         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13019           {
13020               free(cmailCommentList[lastLoadGameNumber - 1]);
13021               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13022           }
13023     }
13024
13025     if (cmailOldMove == -1) {
13026         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13027         return FALSE;
13028     }
13029
13030     if (currentMove > cmailOldMove + 1) {
13031         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13032         return FALSE;
13033     }
13034
13035     if (currentMove < cmailOldMove) {
13036         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13037         return FALSE;
13038     }
13039
13040     if (forwardMostMove > currentMove) {
13041         /* Silently truncate extra moves */
13042         TruncateGame();
13043     }
13044
13045     if (   (currentMove == cmailOldMove + 1)
13046         || (   (currentMove == cmailOldMove)
13047             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13048                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13049         if (gameInfo.result != GameUnfinished) {
13050             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13051         }
13052
13053         if (commentList[currentMove] != NULL) {
13054             cmailCommentList[lastLoadGameNumber - 1]
13055               = StrSave(commentList[currentMove]);
13056         }
13057         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13058
13059         if (appData.debugMode)
13060           fprintf(debugFP, "Saving %s for game %d\n",
13061                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13062
13063         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13064
13065         f = fopen(string, "w");
13066         if (appData.oldSaveStyle) {
13067             SaveGameOldStyle(f); /* also closes the file */
13068
13069             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13070             f = fopen(string, "w");
13071             SavePosition(f, 0, NULL); /* also closes the file */
13072         } else {
13073             fprintf(f, "{--------------\n");
13074             PrintPosition(f, currentMove);
13075             fprintf(f, "--------------}\n\n");
13076
13077             SaveGame(f, 0, NULL); /* also closes the file*/
13078         }
13079
13080         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13081         nCmailMovesRegistered ++;
13082     } else if (nCmailGames == 1) {
13083         DisplayError(_("You have not made a move yet"), 0);
13084         return FALSE;
13085     }
13086
13087     return TRUE;
13088 }
13089
13090 void
13091 MailMoveEvent ()
13092 {
13093 #if !WIN32
13094     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13095     FILE *commandOutput;
13096     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13097     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13098     int nBuffers;
13099     int i;
13100     int archived;
13101     char *arcDir;
13102
13103     if (! cmailMsgLoaded) {
13104         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13105         return;
13106     }
13107
13108     if (nCmailGames == nCmailResults) {
13109         DisplayError(_("No unfinished games"), 0);
13110         return;
13111     }
13112
13113 #if CMAIL_PROHIBIT_REMAIL
13114     if (cmailMailedMove) {
13115       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);
13116         DisplayError(msg, 0);
13117         return;
13118     }
13119 #endif
13120
13121     if (! (cmailMailedMove || RegisterMove())) return;
13122
13123     if (   cmailMailedMove
13124         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13125       snprintf(string, MSG_SIZ, partCommandString,
13126                appData.debugMode ? " -v" : "", appData.cmailGameName);
13127         commandOutput = popen(string, "r");
13128
13129         if (commandOutput == NULL) {
13130             DisplayError(_("Failed to invoke cmail"), 0);
13131         } else {
13132             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13133                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13134             }
13135             if (nBuffers > 1) {
13136                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13137                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13138                 nBytes = MSG_SIZ - 1;
13139             } else {
13140                 (void) memcpy(msg, buffer, nBytes);
13141             }
13142             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13143
13144             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13145                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13146
13147                 archived = TRUE;
13148                 for (i = 0; i < nCmailGames; i ++) {
13149                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13150                         archived = FALSE;
13151                     }
13152                 }
13153                 if (   archived
13154                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13155                         != NULL)) {
13156                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13157                            arcDir,
13158                            appData.cmailGameName,
13159                            gameInfo.date);
13160                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13161                     cmailMsgLoaded = FALSE;
13162                 }
13163             }
13164
13165             DisplayInformation(msg);
13166             pclose(commandOutput);
13167         }
13168     } else {
13169         if ((*cmailMsg) != '\0') {
13170             DisplayInformation(cmailMsg);
13171         }
13172     }
13173
13174     return;
13175 #endif /* !WIN32 */
13176 }
13177
13178 char *
13179 CmailMsg ()
13180 {
13181 #if WIN32
13182     return NULL;
13183 #else
13184     int  prependComma = 0;
13185     char number[5];
13186     char string[MSG_SIZ];       /* Space for game-list */
13187     int  i;
13188
13189     if (!cmailMsgLoaded) return "";
13190
13191     if (cmailMailedMove) {
13192       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13193     } else {
13194         /* Create a list of games left */
13195       snprintf(string, MSG_SIZ, "[");
13196         for (i = 0; i < nCmailGames; i ++) {
13197             if (! (   cmailMoveRegistered[i]
13198                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13199                 if (prependComma) {
13200                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13201                 } else {
13202                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13203                     prependComma = 1;
13204                 }
13205
13206                 strcat(string, number);
13207             }
13208         }
13209         strcat(string, "]");
13210
13211         if (nCmailMovesRegistered + nCmailResults == 0) {
13212             switch (nCmailGames) {
13213               case 1:
13214                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13215                 break;
13216
13217               case 2:
13218                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13219                 break;
13220
13221               default:
13222                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13223                          nCmailGames);
13224                 break;
13225             }
13226         } else {
13227             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13228               case 1:
13229                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13230                          string);
13231                 break;
13232
13233               case 0:
13234                 if (nCmailResults == nCmailGames) {
13235                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13236                 } else {
13237                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13238                 }
13239                 break;
13240
13241               default:
13242                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13243                          string);
13244             }
13245         }
13246     }
13247     return cmailMsg;
13248 #endif /* WIN32 */
13249 }
13250
13251 void
13252 ResetGameEvent ()
13253 {
13254     if (gameMode == Training)
13255       SetTrainingModeOff();
13256
13257     Reset(TRUE, TRUE);
13258     cmailMsgLoaded = FALSE;
13259     if (appData.icsActive) {
13260       SendToICS(ics_prefix);
13261       SendToICS("refresh\n");
13262     }
13263 }
13264
13265 void
13266 ExitEvent (int status)
13267 {
13268     exiting++;
13269     if (exiting > 2) {
13270       /* Give up on clean exit */
13271       exit(status);
13272     }
13273     if (exiting > 1) {
13274       /* Keep trying for clean exit */
13275       return;
13276     }
13277
13278     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13279
13280     if (telnetISR != NULL) {
13281       RemoveInputSource(telnetISR);
13282     }
13283     if (icsPR != NoProc) {
13284       DestroyChildProcess(icsPR, TRUE);
13285     }
13286
13287     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13288     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13289
13290     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13291     /* make sure this other one finishes before killing it!                  */
13292     if(endingGame) { int count = 0;
13293         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13294         while(endingGame && count++ < 10) DoSleep(1);
13295         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13296     }
13297
13298     /* Kill off chess programs */
13299     if (first.pr != NoProc) {
13300         ExitAnalyzeMode();
13301
13302         DoSleep( appData.delayBeforeQuit );
13303         SendToProgram("quit\n", &first);
13304         DoSleep( appData.delayAfterQuit );
13305         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13306     }
13307     if (second.pr != NoProc) {
13308         DoSleep( appData.delayBeforeQuit );
13309         SendToProgram("quit\n", &second);
13310         DoSleep( appData.delayAfterQuit );
13311         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13312     }
13313     if (first.isr != NULL) {
13314         RemoveInputSource(first.isr);
13315     }
13316     if (second.isr != NULL) {
13317         RemoveInputSource(second.isr);
13318     }
13319
13320     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13321     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13322
13323     ShutDownFrontEnd();
13324     exit(status);
13325 }
13326
13327 void
13328 PauseEvent ()
13329 {
13330     if (appData.debugMode)
13331         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13332     if (pausing) {
13333         pausing = FALSE;
13334         ModeHighlight();
13335         if (gameMode == MachinePlaysWhite ||
13336             gameMode == MachinePlaysBlack) {
13337             StartClocks();
13338         } else {
13339             DisplayBothClocks();
13340         }
13341         if (gameMode == PlayFromGameFile) {
13342             if (appData.timeDelay >= 0)
13343                 AutoPlayGameLoop();
13344         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13345             Reset(FALSE, TRUE);
13346             SendToICS(ics_prefix);
13347             SendToICS("refresh\n");
13348         } else if (currentMove < forwardMostMove) {
13349             ForwardInner(forwardMostMove);
13350         }
13351         pauseExamInvalid = FALSE;
13352     } else {
13353         switch (gameMode) {
13354           default:
13355             return;
13356           case IcsExamining:
13357             pauseExamForwardMostMove = forwardMostMove;
13358             pauseExamInvalid = FALSE;
13359             /* fall through */
13360           case IcsObserving:
13361           case IcsPlayingWhite:
13362           case IcsPlayingBlack:
13363             pausing = TRUE;
13364             ModeHighlight();
13365             return;
13366           case PlayFromGameFile:
13367             (void) StopLoadGameTimer();
13368             pausing = TRUE;
13369             ModeHighlight();
13370             break;
13371           case BeginningOfGame:
13372             if (appData.icsActive) return;
13373             /* else fall through */
13374           case MachinePlaysWhite:
13375           case MachinePlaysBlack:
13376           case TwoMachinesPlay:
13377             if (forwardMostMove == 0)
13378               return;           /* don't pause if no one has moved */
13379             if ((gameMode == MachinePlaysWhite &&
13380                  !WhiteOnMove(forwardMostMove)) ||
13381                 (gameMode == MachinePlaysBlack &&
13382                  WhiteOnMove(forwardMostMove))) {
13383                 StopClocks();
13384             }
13385           case AnalyzeMode:
13386             pausing = TRUE;
13387             ModeHighlight();
13388             break;
13389         }
13390     }
13391 }
13392
13393 void
13394 EditCommentEvent ()
13395 {
13396     char title[MSG_SIZ];
13397
13398     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13399       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13400     } else {
13401       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13402                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13403                parseList[currentMove - 1]);
13404     }
13405
13406     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13407 }
13408
13409
13410 void
13411 EditTagsEvent ()
13412 {
13413     char *tags = PGNTags(&gameInfo);
13414     bookUp = FALSE;
13415     EditTagsPopUp(tags, NULL);
13416     free(tags);
13417 }
13418
13419 void
13420 ToggleSecond ()
13421 {
13422   if(second.analyzing) {
13423     SendToProgram("exit\n", &second);
13424     second.analyzing = FALSE;
13425   } else {
13426     if (second.pr == NoProc) StartChessProgram(&second);
13427     InitChessProgram(&second, FALSE);
13428     FeedMovesToProgram(&second, currentMove);
13429
13430     SendToProgram("analyze\n", &second);
13431     second.analyzing = TRUE;
13432   }
13433 }
13434
13435 void
13436 AnalyzeModeEvent ()
13437 {
13438     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13439     if (appData.noChessProgram || gameMode == AnalyzeMode)
13440       return;
13441
13442     if (gameMode != AnalyzeFile) {
13443         if (!appData.icsEngineAnalyze) {
13444                EditGameEvent();
13445                if (gameMode != EditGame) return;
13446         }
13447         ResurrectChessProgram();
13448         SendToProgram("analyze\n", &first);
13449         first.analyzing = TRUE;
13450         /*first.maybeThinking = TRUE;*/
13451         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13452         EngineOutputPopUp();
13453     }
13454     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13455     pausing = FALSE;
13456     ModeHighlight();
13457     SetGameInfo();
13458
13459     StartAnalysisClock();
13460     GetTimeMark(&lastNodeCountTime);
13461     lastNodeCount = 0;
13462 }
13463
13464 void
13465 AnalyzeFileEvent ()
13466 {
13467     if (appData.noChessProgram || gameMode == AnalyzeFile)
13468       return;
13469
13470     if (gameMode != AnalyzeMode) {
13471         EditGameEvent();
13472         if (gameMode != EditGame) return;
13473         ResurrectChessProgram();
13474         SendToProgram("analyze\n", &first);
13475         first.analyzing = TRUE;
13476         /*first.maybeThinking = TRUE;*/
13477         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13478         EngineOutputPopUp();
13479     }
13480     gameMode = AnalyzeFile;
13481     pausing = FALSE;
13482     ModeHighlight();
13483     SetGameInfo();
13484
13485     StartAnalysisClock();
13486     GetTimeMark(&lastNodeCountTime);
13487     lastNodeCount = 0;
13488     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13489 }
13490
13491 void
13492 MachineWhiteEvent ()
13493 {
13494     char buf[MSG_SIZ];
13495     char *bookHit = NULL;
13496
13497     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13498       return;
13499
13500
13501     if (gameMode == PlayFromGameFile ||
13502         gameMode == TwoMachinesPlay  ||
13503         gameMode == Training         ||
13504         gameMode == AnalyzeMode      ||
13505         gameMode == EndOfGame)
13506         EditGameEvent();
13507
13508     if (gameMode == EditPosition)
13509         EditPositionDone(TRUE);
13510
13511     if (!WhiteOnMove(currentMove)) {
13512         DisplayError(_("It is not White's turn"), 0);
13513         return;
13514     }
13515
13516     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13517       ExitAnalyzeMode();
13518
13519     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13520         gameMode == AnalyzeFile)
13521         TruncateGame();
13522
13523     ResurrectChessProgram();    /* in case it isn't running */
13524     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13525         gameMode = MachinePlaysWhite;
13526         ResetClocks();
13527     } else
13528     gameMode = MachinePlaysWhite;
13529     pausing = FALSE;
13530     ModeHighlight();
13531     SetGameInfo();
13532     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13533     DisplayTitle(buf);
13534     if (first.sendName) {
13535       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13536       SendToProgram(buf, &first);
13537     }
13538     if (first.sendTime) {
13539       if (first.useColors) {
13540         SendToProgram("black\n", &first); /*gnu kludge*/
13541       }
13542       SendTimeRemaining(&first, TRUE);
13543     }
13544     if (first.useColors) {
13545       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13546     }
13547     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13548     SetMachineThinkingEnables();
13549     first.maybeThinking = TRUE;
13550     StartClocks();
13551     firstMove = FALSE;
13552
13553     if (appData.autoFlipView && !flipView) {
13554       flipView = !flipView;
13555       DrawPosition(FALSE, NULL);
13556       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13557     }
13558
13559     if(bookHit) { // [HGM] book: simulate book reply
13560         static char bookMove[MSG_SIZ]; // a bit generous?
13561
13562         programStats.nodes = programStats.depth = programStats.time =
13563         programStats.score = programStats.got_only_move = 0;
13564         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13565
13566         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13567         strcat(bookMove, bookHit);
13568         HandleMachineMove(bookMove, &first);
13569     }
13570 }
13571
13572 void
13573 MachineBlackEvent ()
13574 {
13575   char buf[MSG_SIZ];
13576   char *bookHit = NULL;
13577
13578     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13579         return;
13580
13581
13582     if (gameMode == PlayFromGameFile ||
13583         gameMode == TwoMachinesPlay  ||
13584         gameMode == Training         ||
13585         gameMode == AnalyzeMode      ||
13586         gameMode == EndOfGame)
13587         EditGameEvent();
13588
13589     if (gameMode == EditPosition)
13590         EditPositionDone(TRUE);
13591
13592     if (WhiteOnMove(currentMove)) {
13593         DisplayError(_("It is not Black's turn"), 0);
13594         return;
13595     }
13596
13597     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13598       ExitAnalyzeMode();
13599
13600     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13601         gameMode == AnalyzeFile)
13602         TruncateGame();
13603
13604     ResurrectChessProgram();    /* in case it isn't running */
13605     gameMode = MachinePlaysBlack;
13606     pausing = FALSE;
13607     ModeHighlight();
13608     SetGameInfo();
13609     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13610     DisplayTitle(buf);
13611     if (first.sendName) {
13612       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13613       SendToProgram(buf, &first);
13614     }
13615     if (first.sendTime) {
13616       if (first.useColors) {
13617         SendToProgram("white\n", &first); /*gnu kludge*/
13618       }
13619       SendTimeRemaining(&first, FALSE);
13620     }
13621     if (first.useColors) {
13622       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13623     }
13624     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13625     SetMachineThinkingEnables();
13626     first.maybeThinking = TRUE;
13627     StartClocks();
13628
13629     if (appData.autoFlipView && flipView) {
13630       flipView = !flipView;
13631       DrawPosition(FALSE, NULL);
13632       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13633     }
13634     if(bookHit) { // [HGM] book: simulate book reply
13635         static char bookMove[MSG_SIZ]; // a bit generous?
13636
13637         programStats.nodes = programStats.depth = programStats.time =
13638         programStats.score = programStats.got_only_move = 0;
13639         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13640
13641         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13642         strcat(bookMove, bookHit);
13643         HandleMachineMove(bookMove, &first);
13644     }
13645 }
13646
13647
13648 void
13649 DisplayTwoMachinesTitle ()
13650 {
13651     char buf[MSG_SIZ];
13652     if (appData.matchGames > 0) {
13653         if(appData.tourneyFile[0]) {
13654           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13655                    gameInfo.white, _("vs."), gameInfo.black,
13656                    nextGame+1, appData.matchGames+1,
13657                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13658         } else 
13659         if (first.twoMachinesColor[0] == 'w') {
13660           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13661                    gameInfo.white, _("vs."),  gameInfo.black,
13662                    first.matchWins, second.matchWins,
13663                    matchGame - 1 - (first.matchWins + second.matchWins));
13664         } else {
13665           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13666                    gameInfo.white, _("vs."), gameInfo.black,
13667                    second.matchWins, first.matchWins,
13668                    matchGame - 1 - (first.matchWins + second.matchWins));
13669         }
13670     } else {
13671       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13672     }
13673     DisplayTitle(buf);
13674 }
13675
13676 void
13677 SettingsMenuIfReady ()
13678 {
13679   if (second.lastPing != second.lastPong) {
13680     DisplayMessage("", _("Waiting for second chess program"));
13681     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13682     return;
13683   }
13684   ThawUI();
13685   DisplayMessage("", "");
13686   SettingsPopUp(&second);
13687 }
13688
13689 int
13690 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13691 {
13692     char buf[MSG_SIZ];
13693     if (cps->pr == NoProc) {
13694         StartChessProgram(cps);
13695         if (cps->protocolVersion == 1) {
13696           retry();
13697         } else {
13698           /* kludge: allow timeout for initial "feature" command */
13699           FreezeUI();
13700           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13701           DisplayMessage("", buf);
13702           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13703         }
13704         return 1;
13705     }
13706     return 0;
13707 }
13708
13709 void
13710 TwoMachinesEvent P((void))
13711 {
13712     int i;
13713     char buf[MSG_SIZ];
13714     ChessProgramState *onmove;
13715     char *bookHit = NULL;
13716     static int stalling = 0;
13717     TimeMark now;
13718     long wait;
13719
13720     if (appData.noChessProgram) return;
13721
13722     switch (gameMode) {
13723       case TwoMachinesPlay:
13724         return;
13725       case MachinePlaysWhite:
13726       case MachinePlaysBlack:
13727         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13728             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13729             return;
13730         }
13731         /* fall through */
13732       case BeginningOfGame:
13733       case PlayFromGameFile:
13734       case EndOfGame:
13735         EditGameEvent();
13736         if (gameMode != EditGame) return;
13737         break;
13738       case EditPosition:
13739         EditPositionDone(TRUE);
13740         break;
13741       case AnalyzeMode:
13742       case AnalyzeFile:
13743         ExitAnalyzeMode();
13744         break;
13745       case EditGame:
13746       default:
13747         break;
13748     }
13749
13750 //    forwardMostMove = currentMove;
13751     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13752
13753     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13754
13755     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13756     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13757       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13758       return;
13759     }
13760
13761     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13762         DisplayError("second engine does not play this", 0);
13763         return;
13764     }
13765
13766     if(!stalling) {
13767       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13768       SendToProgram("force\n", &second);
13769       stalling = 1;
13770       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13771       return;
13772     }
13773     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13774     if(appData.matchPause>10000 || appData.matchPause<10)
13775                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13776     wait = SubtractTimeMarks(&now, &pauseStart);
13777     if(wait < appData.matchPause) {
13778         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13779         return;
13780     }
13781     // we are now committed to starting the game
13782     stalling = 0;
13783     DisplayMessage("", "");
13784     if (startedFromSetupPosition) {
13785         SendBoard(&second, backwardMostMove);
13786     if (appData.debugMode) {
13787         fprintf(debugFP, "Two Machines\n");
13788     }
13789     }
13790     for (i = backwardMostMove; i < forwardMostMove; i++) {
13791         SendMoveToProgram(i, &second);
13792     }
13793
13794     gameMode = TwoMachinesPlay;
13795     pausing = FALSE;
13796     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13797     SetGameInfo();
13798     DisplayTwoMachinesTitle();
13799     firstMove = TRUE;
13800     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13801         onmove = &first;
13802     } else {
13803         onmove = &second;
13804     }
13805     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13806     SendToProgram(first.computerString, &first);
13807     if (first.sendName) {
13808       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13809       SendToProgram(buf, &first);
13810     }
13811     SendToProgram(second.computerString, &second);
13812     if (second.sendName) {
13813       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13814       SendToProgram(buf, &second);
13815     }
13816
13817     ResetClocks();
13818     if (!first.sendTime || !second.sendTime) {
13819         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13820         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13821     }
13822     if (onmove->sendTime) {
13823       if (onmove->useColors) {
13824         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13825       }
13826       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13827     }
13828     if (onmove->useColors) {
13829       SendToProgram(onmove->twoMachinesColor, onmove);
13830     }
13831     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13832 //    SendToProgram("go\n", onmove);
13833     onmove->maybeThinking = TRUE;
13834     SetMachineThinkingEnables();
13835
13836     StartClocks();
13837
13838     if(bookHit) { // [HGM] book: simulate book reply
13839         static char bookMove[MSG_SIZ]; // a bit generous?
13840
13841         programStats.nodes = programStats.depth = programStats.time =
13842         programStats.score = programStats.got_only_move = 0;
13843         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13844
13845         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13846         strcat(bookMove, bookHit);
13847         savedMessage = bookMove; // args for deferred call
13848         savedState = onmove;
13849         ScheduleDelayedEvent(DeferredBookMove, 1);
13850     }
13851 }
13852
13853 void
13854 TrainingEvent ()
13855 {
13856     if (gameMode == Training) {
13857       SetTrainingModeOff();
13858       gameMode = PlayFromGameFile;
13859       DisplayMessage("", _("Training mode off"));
13860     } else {
13861       gameMode = Training;
13862       animateTraining = appData.animate;
13863
13864       /* make sure we are not already at the end of the game */
13865       if (currentMove < forwardMostMove) {
13866         SetTrainingModeOn();
13867         DisplayMessage("", _("Training mode on"));
13868       } else {
13869         gameMode = PlayFromGameFile;
13870         DisplayError(_("Already at end of game"), 0);
13871       }
13872     }
13873     ModeHighlight();
13874 }
13875
13876 void
13877 IcsClientEvent ()
13878 {
13879     if (!appData.icsActive) return;
13880     switch (gameMode) {
13881       case IcsPlayingWhite:
13882       case IcsPlayingBlack:
13883       case IcsObserving:
13884       case IcsIdle:
13885       case BeginningOfGame:
13886       case IcsExamining:
13887         return;
13888
13889       case EditGame:
13890         break;
13891
13892       case EditPosition:
13893         EditPositionDone(TRUE);
13894         break;
13895
13896       case AnalyzeMode:
13897       case AnalyzeFile:
13898         ExitAnalyzeMode();
13899         break;
13900
13901       default:
13902         EditGameEvent();
13903         break;
13904     }
13905
13906     gameMode = IcsIdle;
13907     ModeHighlight();
13908     return;
13909 }
13910
13911 void
13912 EditGameEvent ()
13913 {
13914     int i;
13915
13916     switch (gameMode) {
13917       case Training:
13918         SetTrainingModeOff();
13919         break;
13920       case MachinePlaysWhite:
13921       case MachinePlaysBlack:
13922       case BeginningOfGame:
13923         SendToProgram("force\n", &first);
13924         SetUserThinkingEnables();
13925         break;
13926       case PlayFromGameFile:
13927         (void) StopLoadGameTimer();
13928         if (gameFileFP != NULL) {
13929             gameFileFP = NULL;
13930         }
13931         break;
13932       case EditPosition:
13933         EditPositionDone(TRUE);
13934         break;
13935       case AnalyzeMode:
13936       case AnalyzeFile:
13937         ExitAnalyzeMode();
13938         SendToProgram("force\n", &first);
13939         break;
13940       case TwoMachinesPlay:
13941         GameEnds(EndOfFile, NULL, GE_PLAYER);
13942         ResurrectChessProgram();
13943         SetUserThinkingEnables();
13944         break;
13945       case EndOfGame:
13946         ResurrectChessProgram();
13947         break;
13948       case IcsPlayingBlack:
13949       case IcsPlayingWhite:
13950         DisplayError(_("Warning: You are still playing a game"), 0);
13951         break;
13952       case IcsObserving:
13953         DisplayError(_("Warning: You are still observing a game"), 0);
13954         break;
13955       case IcsExamining:
13956         DisplayError(_("Warning: You are still examining a game"), 0);
13957         break;
13958       case IcsIdle:
13959         break;
13960       case EditGame:
13961       default:
13962         return;
13963     }
13964
13965     pausing = FALSE;
13966     StopClocks();
13967     first.offeredDraw = second.offeredDraw = 0;
13968
13969     if (gameMode == PlayFromGameFile) {
13970         whiteTimeRemaining = timeRemaining[0][currentMove];
13971         blackTimeRemaining = timeRemaining[1][currentMove];
13972         DisplayTitle("");
13973     }
13974
13975     if (gameMode == MachinePlaysWhite ||
13976         gameMode == MachinePlaysBlack ||
13977         gameMode == TwoMachinesPlay ||
13978         gameMode == EndOfGame) {
13979         i = forwardMostMove;
13980         while (i > currentMove) {
13981             SendToProgram("undo\n", &first);
13982             i--;
13983         }
13984         if(!adjustedClock) {
13985         whiteTimeRemaining = timeRemaining[0][currentMove];
13986         blackTimeRemaining = timeRemaining[1][currentMove];
13987         DisplayBothClocks();
13988         }
13989         if (whiteFlag || blackFlag) {
13990             whiteFlag = blackFlag = 0;
13991         }
13992         DisplayTitle("");
13993     }
13994
13995     gameMode = EditGame;
13996     ModeHighlight();
13997     SetGameInfo();
13998 }
13999
14000
14001 void
14002 EditPositionEvent ()
14003 {
14004     if (gameMode == EditPosition) {
14005         EditGameEvent();
14006         return;
14007     }
14008
14009     EditGameEvent();
14010     if (gameMode != EditGame) return;
14011
14012     gameMode = EditPosition;
14013     ModeHighlight();
14014     SetGameInfo();
14015     if (currentMove > 0)
14016       CopyBoard(boards[0], boards[currentMove]);
14017
14018     blackPlaysFirst = !WhiteOnMove(currentMove);
14019     ResetClocks();
14020     currentMove = forwardMostMove = backwardMostMove = 0;
14021     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14022     DisplayMove(-1);
14023     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14024 }
14025
14026 void
14027 ExitAnalyzeMode ()
14028 {
14029     /* [DM] icsEngineAnalyze - possible call from other functions */
14030     if (appData.icsEngineAnalyze) {
14031         appData.icsEngineAnalyze = FALSE;
14032
14033         DisplayMessage("",_("Close ICS engine analyze..."));
14034     }
14035     if (first.analysisSupport && first.analyzing) {
14036       SendToBoth("exit\n");
14037       first.analyzing = second.analyzing = FALSE;
14038     }
14039     thinkOutput[0] = NULLCHAR;
14040 }
14041
14042 void
14043 EditPositionDone (Boolean fakeRights)
14044 {
14045     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14046
14047     startedFromSetupPosition = TRUE;
14048     InitChessProgram(&first, FALSE);
14049     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14050       boards[0][EP_STATUS] = EP_NONE;
14051       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14052       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14053         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14054         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14055       } else boards[0][CASTLING][2] = NoRights;
14056       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14057         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14058         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14059       } else boards[0][CASTLING][5] = NoRights;
14060       if(gameInfo.variant == VariantSChess) {
14061         int i;
14062         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14063           boards[0][VIRGIN][i] = 0;
14064           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14065           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14066         }
14067       }
14068     }
14069     SendToProgram("force\n", &first);
14070     if (blackPlaysFirst) {
14071         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14072         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14073         currentMove = forwardMostMove = backwardMostMove = 1;
14074         CopyBoard(boards[1], boards[0]);
14075     } else {
14076         currentMove = forwardMostMove = backwardMostMove = 0;
14077     }
14078     SendBoard(&first, forwardMostMove);
14079     if (appData.debugMode) {
14080         fprintf(debugFP, "EditPosDone\n");
14081     }
14082     DisplayTitle("");
14083     DisplayMessage("", "");
14084     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14085     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14086     gameMode = EditGame;
14087     ModeHighlight();
14088     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14089     ClearHighlights(); /* [AS] */
14090 }
14091
14092 /* Pause for `ms' milliseconds */
14093 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14094 void
14095 TimeDelay (long ms)
14096 {
14097     TimeMark m1, m2;
14098
14099     GetTimeMark(&m1);
14100     do {
14101         GetTimeMark(&m2);
14102     } while (SubtractTimeMarks(&m2, &m1) < ms);
14103 }
14104
14105 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14106 void
14107 SendMultiLineToICS (char *buf)
14108 {
14109     char temp[MSG_SIZ+1], *p;
14110     int len;
14111
14112     len = strlen(buf);
14113     if (len > MSG_SIZ)
14114       len = MSG_SIZ;
14115
14116     strncpy(temp, buf, len);
14117     temp[len] = 0;
14118
14119     p = temp;
14120     while (*p) {
14121         if (*p == '\n' || *p == '\r')
14122           *p = ' ';
14123         ++p;
14124     }
14125
14126     strcat(temp, "\n");
14127     SendToICS(temp);
14128     SendToPlayer(temp, strlen(temp));
14129 }
14130
14131 void
14132 SetWhiteToPlayEvent ()
14133 {
14134     if (gameMode == EditPosition) {
14135         blackPlaysFirst = FALSE;
14136         DisplayBothClocks();    /* works because currentMove is 0 */
14137     } else if (gameMode == IcsExamining) {
14138         SendToICS(ics_prefix);
14139         SendToICS("tomove white\n");
14140     }
14141 }
14142
14143 void
14144 SetBlackToPlayEvent ()
14145 {
14146     if (gameMode == EditPosition) {
14147         blackPlaysFirst = TRUE;
14148         currentMove = 1;        /* kludge */
14149         DisplayBothClocks();
14150         currentMove = 0;
14151     } else if (gameMode == IcsExamining) {
14152         SendToICS(ics_prefix);
14153         SendToICS("tomove black\n");
14154     }
14155 }
14156
14157 void
14158 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14159 {
14160     char buf[MSG_SIZ];
14161     ChessSquare piece = boards[0][y][x];
14162
14163     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14164
14165     switch (selection) {
14166       case ClearBoard:
14167         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14168             SendToICS(ics_prefix);
14169             SendToICS("bsetup clear\n");
14170         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14171             SendToICS(ics_prefix);
14172             SendToICS("clearboard\n");
14173         } else {
14174             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14175                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14176                 for (y = 0; y < BOARD_HEIGHT; y++) {
14177                     if (gameMode == IcsExamining) {
14178                         if (boards[currentMove][y][x] != EmptySquare) {
14179                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14180                                     AAA + x, ONE + y);
14181                             SendToICS(buf);
14182                         }
14183                     } else {
14184                         boards[0][y][x] = p;
14185                     }
14186                 }
14187             }
14188         }
14189         if (gameMode == EditPosition) {
14190             DrawPosition(FALSE, boards[0]);
14191         }
14192         break;
14193
14194       case WhitePlay:
14195         SetWhiteToPlayEvent();
14196         break;
14197
14198       case BlackPlay:
14199         SetBlackToPlayEvent();
14200         break;
14201
14202       case EmptySquare:
14203         if (gameMode == IcsExamining) {
14204             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14205             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14206             SendToICS(buf);
14207         } else {
14208             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14209                 if(x == BOARD_LEFT-2) {
14210                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14211                     boards[0][y][1] = 0;
14212                 } else
14213                 if(x == BOARD_RGHT+1) {
14214                     if(y >= gameInfo.holdingsSize) break;
14215                     boards[0][y][BOARD_WIDTH-2] = 0;
14216                 } else break;
14217             }
14218             boards[0][y][x] = EmptySquare;
14219             DrawPosition(FALSE, boards[0]);
14220         }
14221         break;
14222
14223       case PromotePiece:
14224         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14225            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14226             selection = (ChessSquare) (PROMOTED piece);
14227         } else if(piece == EmptySquare) selection = WhiteSilver;
14228         else selection = (ChessSquare)((int)piece - 1);
14229         goto defaultlabel;
14230
14231       case DemotePiece:
14232         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14233            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14234             selection = (ChessSquare) (DEMOTED piece);
14235         } else if(piece == EmptySquare) selection = BlackSilver;
14236         else selection = (ChessSquare)((int)piece + 1);
14237         goto defaultlabel;
14238
14239       case WhiteQueen:
14240       case BlackQueen:
14241         if(gameInfo.variant == VariantShatranj ||
14242            gameInfo.variant == VariantXiangqi  ||
14243            gameInfo.variant == VariantCourier  ||
14244            gameInfo.variant == VariantMakruk     )
14245             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14246         goto defaultlabel;
14247
14248       case WhiteKing:
14249       case BlackKing:
14250         if(gameInfo.variant == VariantXiangqi)
14251             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14252         if(gameInfo.variant == VariantKnightmate)
14253             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14254       default:
14255         defaultlabel:
14256         if (gameMode == IcsExamining) {
14257             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14258             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14259                      PieceToChar(selection), AAA + x, ONE + y);
14260             SendToICS(buf);
14261         } else {
14262             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14263                 int n;
14264                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14265                     n = PieceToNumber(selection - BlackPawn);
14266                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14267                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14268                     boards[0][BOARD_HEIGHT-1-n][1]++;
14269                 } else
14270                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14271                     n = PieceToNumber(selection);
14272                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14273                     boards[0][n][BOARD_WIDTH-1] = selection;
14274                     boards[0][n][BOARD_WIDTH-2]++;
14275                 }
14276             } else
14277             boards[0][y][x] = selection;
14278             DrawPosition(TRUE, boards[0]);
14279             ClearHighlights();
14280             fromX = fromY = -1;
14281         }
14282         break;
14283     }
14284 }
14285
14286
14287 void
14288 DropMenuEvent (ChessSquare selection, int x, int y)
14289 {
14290     ChessMove moveType;
14291
14292     switch (gameMode) {
14293       case IcsPlayingWhite:
14294       case MachinePlaysBlack:
14295         if (!WhiteOnMove(currentMove)) {
14296             DisplayMoveError(_("It is Black's turn"));
14297             return;
14298         }
14299         moveType = WhiteDrop;
14300         break;
14301       case IcsPlayingBlack:
14302       case MachinePlaysWhite:
14303         if (WhiteOnMove(currentMove)) {
14304             DisplayMoveError(_("It is White's turn"));
14305             return;
14306         }
14307         moveType = BlackDrop;
14308         break;
14309       case EditGame:
14310         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14311         break;
14312       default:
14313         return;
14314     }
14315
14316     if (moveType == BlackDrop && selection < BlackPawn) {
14317       selection = (ChessSquare) ((int) selection
14318                                  + (int) BlackPawn - (int) WhitePawn);
14319     }
14320     if (boards[currentMove][y][x] != EmptySquare) {
14321         DisplayMoveError(_("That square is occupied"));
14322         return;
14323     }
14324
14325     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14326 }
14327
14328 void
14329 AcceptEvent ()
14330 {
14331     /* Accept a pending offer of any kind from opponent */
14332
14333     if (appData.icsActive) {
14334         SendToICS(ics_prefix);
14335         SendToICS("accept\n");
14336     } else if (cmailMsgLoaded) {
14337         if (currentMove == cmailOldMove &&
14338             commentList[cmailOldMove] != NULL &&
14339             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14340                    "Black offers a draw" : "White offers a draw")) {
14341             TruncateGame();
14342             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14343             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14344         } else {
14345             DisplayError(_("There is no pending offer on this move"), 0);
14346             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14347         }
14348     } else {
14349         /* Not used for offers from chess program */
14350     }
14351 }
14352
14353 void
14354 DeclineEvent ()
14355 {
14356     /* Decline a pending offer of any kind from opponent */
14357
14358     if (appData.icsActive) {
14359         SendToICS(ics_prefix);
14360         SendToICS("decline\n");
14361     } else if (cmailMsgLoaded) {
14362         if (currentMove == cmailOldMove &&
14363             commentList[cmailOldMove] != NULL &&
14364             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14365                    "Black offers a draw" : "White offers a draw")) {
14366 #ifdef NOTDEF
14367             AppendComment(cmailOldMove, "Draw declined", TRUE);
14368             DisplayComment(cmailOldMove - 1, "Draw declined");
14369 #endif /*NOTDEF*/
14370         } else {
14371             DisplayError(_("There is no pending offer on this move"), 0);
14372         }
14373     } else {
14374         /* Not used for offers from chess program */
14375     }
14376 }
14377
14378 void
14379 RematchEvent ()
14380 {
14381     /* Issue ICS rematch command */
14382     if (appData.icsActive) {
14383         SendToICS(ics_prefix);
14384         SendToICS("rematch\n");
14385     }
14386 }
14387
14388 void
14389 CallFlagEvent ()
14390 {
14391     /* Call your opponent's flag (claim a win on time) */
14392     if (appData.icsActive) {
14393         SendToICS(ics_prefix);
14394         SendToICS("flag\n");
14395     } else {
14396         switch (gameMode) {
14397           default:
14398             return;
14399           case MachinePlaysWhite:
14400             if (whiteFlag) {
14401                 if (blackFlag)
14402                   GameEnds(GameIsDrawn, "Both players ran out of time",
14403                            GE_PLAYER);
14404                 else
14405                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14406             } else {
14407                 DisplayError(_("Your opponent is not out of time"), 0);
14408             }
14409             break;
14410           case MachinePlaysBlack:
14411             if (blackFlag) {
14412                 if (whiteFlag)
14413                   GameEnds(GameIsDrawn, "Both players ran out of time",
14414                            GE_PLAYER);
14415                 else
14416                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14417             } else {
14418                 DisplayError(_("Your opponent is not out of time"), 0);
14419             }
14420             break;
14421         }
14422     }
14423 }
14424
14425 void
14426 ClockClick (int which)
14427 {       // [HGM] code moved to back-end from winboard.c
14428         if(which) { // black clock
14429           if (gameMode == EditPosition || gameMode == IcsExamining) {
14430             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14431             SetBlackToPlayEvent();
14432           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14433           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14434           } else if (shiftKey) {
14435             AdjustClock(which, -1);
14436           } else if (gameMode == IcsPlayingWhite ||
14437                      gameMode == MachinePlaysBlack) {
14438             CallFlagEvent();
14439           }
14440         } else { // white clock
14441           if (gameMode == EditPosition || gameMode == IcsExamining) {
14442             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14443             SetWhiteToPlayEvent();
14444           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14445           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14446           } else if (shiftKey) {
14447             AdjustClock(which, -1);
14448           } else if (gameMode == IcsPlayingBlack ||
14449                    gameMode == MachinePlaysWhite) {
14450             CallFlagEvent();
14451           }
14452         }
14453 }
14454
14455 void
14456 DrawEvent ()
14457 {
14458     /* Offer draw or accept pending draw offer from opponent */
14459
14460     if (appData.icsActive) {
14461         /* Note: tournament rules require draw offers to be
14462            made after you make your move but before you punch
14463            your clock.  Currently ICS doesn't let you do that;
14464            instead, you immediately punch your clock after making
14465            a move, but you can offer a draw at any time. */
14466
14467         SendToICS(ics_prefix);
14468         SendToICS("draw\n");
14469         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14470     } else if (cmailMsgLoaded) {
14471         if (currentMove == cmailOldMove &&
14472             commentList[cmailOldMove] != NULL &&
14473             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14474                    "Black offers a draw" : "White offers a draw")) {
14475             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14476             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14477         } else if (currentMove == cmailOldMove + 1) {
14478             char *offer = WhiteOnMove(cmailOldMove) ?
14479               "White offers a draw" : "Black offers a draw";
14480             AppendComment(currentMove, offer, TRUE);
14481             DisplayComment(currentMove - 1, offer);
14482             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14483         } else {
14484             DisplayError(_("You must make your move before offering a draw"), 0);
14485             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14486         }
14487     } else if (first.offeredDraw) {
14488         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14489     } else {
14490         if (first.sendDrawOffers) {
14491             SendToProgram("draw\n", &first);
14492             userOfferedDraw = TRUE;
14493         }
14494     }
14495 }
14496
14497 void
14498 AdjournEvent ()
14499 {
14500     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14501
14502     if (appData.icsActive) {
14503         SendToICS(ics_prefix);
14504         SendToICS("adjourn\n");
14505     } else {
14506         /* Currently GNU Chess doesn't offer or accept Adjourns */
14507     }
14508 }
14509
14510
14511 void
14512 AbortEvent ()
14513 {
14514     /* Offer Abort or accept pending Abort offer from opponent */
14515
14516     if (appData.icsActive) {
14517         SendToICS(ics_prefix);
14518         SendToICS("abort\n");
14519     } else {
14520         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14521     }
14522 }
14523
14524 void
14525 ResignEvent ()
14526 {
14527     /* Resign.  You can do this even if it's not your turn. */
14528
14529     if (appData.icsActive) {
14530         SendToICS(ics_prefix);
14531         SendToICS("resign\n");
14532     } else {
14533         switch (gameMode) {
14534           case MachinePlaysWhite:
14535             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14536             break;
14537           case MachinePlaysBlack:
14538             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14539             break;
14540           case EditGame:
14541             if (cmailMsgLoaded) {
14542                 TruncateGame();
14543                 if (WhiteOnMove(cmailOldMove)) {
14544                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14545                 } else {
14546                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14547                 }
14548                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14549             }
14550             break;
14551           default:
14552             break;
14553         }
14554     }
14555 }
14556
14557
14558 void
14559 StopObservingEvent ()
14560 {
14561     /* Stop observing current games */
14562     SendToICS(ics_prefix);
14563     SendToICS("unobserve\n");
14564 }
14565
14566 void
14567 StopExaminingEvent ()
14568 {
14569     /* Stop observing current game */
14570     SendToICS(ics_prefix);
14571     SendToICS("unexamine\n");
14572 }
14573
14574 void
14575 ForwardInner (int target)
14576 {
14577     int limit; int oldSeekGraphUp = seekGraphUp;
14578
14579     if (appData.debugMode)
14580         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14581                 target, currentMove, forwardMostMove);
14582
14583     if (gameMode == EditPosition)
14584       return;
14585
14586     seekGraphUp = FALSE;
14587     MarkTargetSquares(1);
14588
14589     if (gameMode == PlayFromGameFile && !pausing)
14590       PauseEvent();
14591
14592     if (gameMode == IcsExamining && pausing)
14593       limit = pauseExamForwardMostMove;
14594     else
14595       limit = forwardMostMove;
14596
14597     if (target > limit) target = limit;
14598
14599     if (target > 0 && moveList[target - 1][0]) {
14600         int fromX, fromY, toX, toY;
14601         toX = moveList[target - 1][2] - AAA;
14602         toY = moveList[target - 1][3] - ONE;
14603         if (moveList[target - 1][1] == '@') {
14604             if (appData.highlightLastMove) {
14605                 SetHighlights(-1, -1, toX, toY);
14606             }
14607         } else {
14608             fromX = moveList[target - 1][0] - AAA;
14609             fromY = moveList[target - 1][1] - ONE;
14610             if (target == currentMove + 1) {
14611                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14612             }
14613             if (appData.highlightLastMove) {
14614                 SetHighlights(fromX, fromY, toX, toY);
14615             }
14616         }
14617     }
14618     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14619         gameMode == Training || gameMode == PlayFromGameFile ||
14620         gameMode == AnalyzeFile) {
14621         while (currentMove < target) {
14622             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14623             SendMoveToProgram(currentMove++, &first);
14624         }
14625     } else {
14626         currentMove = target;
14627     }
14628
14629     if (gameMode == EditGame || gameMode == EndOfGame) {
14630         whiteTimeRemaining = timeRemaining[0][currentMove];
14631         blackTimeRemaining = timeRemaining[1][currentMove];
14632     }
14633     DisplayBothClocks();
14634     DisplayMove(currentMove - 1);
14635     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14636     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14637     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14638         DisplayComment(currentMove - 1, commentList[currentMove]);
14639     }
14640     ClearMap(); // [HGM] exclude: invalidate map
14641 }
14642
14643
14644 void
14645 ForwardEvent ()
14646 {
14647     if (gameMode == IcsExamining && !pausing) {
14648         SendToICS(ics_prefix);
14649         SendToICS("forward\n");
14650     } else {
14651         ForwardInner(currentMove + 1);
14652     }
14653 }
14654
14655 void
14656 ToEndEvent ()
14657 {
14658     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14659         /* to optimze, we temporarily turn off analysis mode while we feed
14660          * the remaining moves to the engine. Otherwise we get analysis output
14661          * after each move.
14662          */
14663         if (first.analysisSupport) {
14664           SendToProgram("exit\nforce\n", &first);
14665           first.analyzing = FALSE;
14666         }
14667     }
14668
14669     if (gameMode == IcsExamining && !pausing) {
14670         SendToICS(ics_prefix);
14671         SendToICS("forward 999999\n");
14672     } else {
14673         ForwardInner(forwardMostMove);
14674     }
14675
14676     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14677         /* we have fed all the moves, so reactivate analysis mode */
14678         SendToProgram("analyze\n", &first);
14679         first.analyzing = TRUE;
14680         /*first.maybeThinking = TRUE;*/
14681         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14682     }
14683 }
14684
14685 void
14686 BackwardInner (int target)
14687 {
14688     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14689
14690     if (appData.debugMode)
14691         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14692                 target, currentMove, forwardMostMove);
14693
14694     if (gameMode == EditPosition) return;
14695     seekGraphUp = FALSE;
14696     MarkTargetSquares(1);
14697     if (currentMove <= backwardMostMove) {
14698         ClearHighlights();
14699         DrawPosition(full_redraw, boards[currentMove]);
14700         return;
14701     }
14702     if (gameMode == PlayFromGameFile && !pausing)
14703       PauseEvent();
14704
14705     if (moveList[target][0]) {
14706         int fromX, fromY, toX, toY;
14707         toX = moveList[target][2] - AAA;
14708         toY = moveList[target][3] - ONE;
14709         if (moveList[target][1] == '@') {
14710             if (appData.highlightLastMove) {
14711                 SetHighlights(-1, -1, toX, toY);
14712             }
14713         } else {
14714             fromX = moveList[target][0] - AAA;
14715             fromY = moveList[target][1] - ONE;
14716             if (target == currentMove - 1) {
14717                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14718             }
14719             if (appData.highlightLastMove) {
14720                 SetHighlights(fromX, fromY, toX, toY);
14721             }
14722         }
14723     }
14724     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14725         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14726         while (currentMove > target) {
14727             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14728                 // null move cannot be undone. Reload program with move history before it.
14729                 int i;
14730                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14731                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14732                 }
14733                 SendBoard(&first, i); 
14734               if(second.analyzing) SendBoard(&second, i);
14735                 for(currentMove=i; currentMove<target; currentMove++) {
14736                     SendMoveToProgram(currentMove, &first);
14737                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14738                 }
14739                 break;
14740             }
14741             SendToBoth("undo\n");
14742             currentMove--;
14743         }
14744     } else {
14745         currentMove = target;
14746     }
14747
14748     if (gameMode == EditGame || gameMode == EndOfGame) {
14749         whiteTimeRemaining = timeRemaining[0][currentMove];
14750         blackTimeRemaining = timeRemaining[1][currentMove];
14751     }
14752     DisplayBothClocks();
14753     DisplayMove(currentMove - 1);
14754     DrawPosition(full_redraw, boards[currentMove]);
14755     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14756     // [HGM] PV info: routine tests if comment empty
14757     DisplayComment(currentMove - 1, commentList[currentMove]);
14758     ClearMap(); // [HGM] exclude: invalidate map
14759 }
14760
14761 void
14762 BackwardEvent ()
14763 {
14764     if (gameMode == IcsExamining && !pausing) {
14765         SendToICS(ics_prefix);
14766         SendToICS("backward\n");
14767     } else {
14768         BackwardInner(currentMove - 1);
14769     }
14770 }
14771
14772 void
14773 ToStartEvent ()
14774 {
14775     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14776         /* to optimize, we temporarily turn off analysis mode while we undo
14777          * all the moves. Otherwise we get analysis output after each undo.
14778          */
14779         if (first.analysisSupport) {
14780           SendToProgram("exit\nforce\n", &first);
14781           first.analyzing = FALSE;
14782         }
14783     }
14784
14785     if (gameMode == IcsExamining && !pausing) {
14786         SendToICS(ics_prefix);
14787         SendToICS("backward 999999\n");
14788     } else {
14789         BackwardInner(backwardMostMove);
14790     }
14791
14792     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14793         /* we have fed all the moves, so reactivate analysis mode */
14794         SendToProgram("analyze\n", &first);
14795         first.analyzing = TRUE;
14796         /*first.maybeThinking = TRUE;*/
14797         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14798     }
14799 }
14800
14801 void
14802 ToNrEvent (int to)
14803 {
14804   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14805   if (to >= forwardMostMove) to = forwardMostMove;
14806   if (to <= backwardMostMove) to = backwardMostMove;
14807   if (to < currentMove) {
14808     BackwardInner(to);
14809   } else {
14810     ForwardInner(to);
14811   }
14812 }
14813
14814 void
14815 RevertEvent (Boolean annotate)
14816 {
14817     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14818         return;
14819     }
14820     if (gameMode != IcsExamining) {
14821         DisplayError(_("You are not examining a game"), 0);
14822         return;
14823     }
14824     if (pausing) {
14825         DisplayError(_("You can't revert while pausing"), 0);
14826         return;
14827     }
14828     SendToICS(ics_prefix);
14829     SendToICS("revert\n");
14830 }
14831
14832 void
14833 RetractMoveEvent ()
14834 {
14835     switch (gameMode) {
14836       case MachinePlaysWhite:
14837       case MachinePlaysBlack:
14838         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14839             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14840             return;
14841         }
14842         if (forwardMostMove < 2) return;
14843         currentMove = forwardMostMove = forwardMostMove - 2;
14844         whiteTimeRemaining = timeRemaining[0][currentMove];
14845         blackTimeRemaining = timeRemaining[1][currentMove];
14846         DisplayBothClocks();
14847         DisplayMove(currentMove - 1);
14848         ClearHighlights();/*!! could figure this out*/
14849         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14850         SendToProgram("remove\n", &first);
14851         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14852         break;
14853
14854       case BeginningOfGame:
14855       default:
14856         break;
14857
14858       case IcsPlayingWhite:
14859       case IcsPlayingBlack:
14860         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14861             SendToICS(ics_prefix);
14862             SendToICS("takeback 2\n");
14863         } else {
14864             SendToICS(ics_prefix);
14865             SendToICS("takeback 1\n");
14866         }
14867         break;
14868     }
14869 }
14870
14871 void
14872 MoveNowEvent ()
14873 {
14874     ChessProgramState *cps;
14875
14876     switch (gameMode) {
14877       case MachinePlaysWhite:
14878         if (!WhiteOnMove(forwardMostMove)) {
14879             DisplayError(_("It is your turn"), 0);
14880             return;
14881         }
14882         cps = &first;
14883         break;
14884       case MachinePlaysBlack:
14885         if (WhiteOnMove(forwardMostMove)) {
14886             DisplayError(_("It is your turn"), 0);
14887             return;
14888         }
14889         cps = &first;
14890         break;
14891       case TwoMachinesPlay:
14892         if (WhiteOnMove(forwardMostMove) ==
14893             (first.twoMachinesColor[0] == 'w')) {
14894             cps = &first;
14895         } else {
14896             cps = &second;
14897         }
14898         break;
14899       case BeginningOfGame:
14900       default:
14901         return;
14902     }
14903     SendToProgram("?\n", cps);
14904 }
14905
14906 void
14907 TruncateGameEvent ()
14908 {
14909     EditGameEvent();
14910     if (gameMode != EditGame) return;
14911     TruncateGame();
14912 }
14913
14914 void
14915 TruncateGame ()
14916 {
14917     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14918     if (forwardMostMove > currentMove) {
14919         if (gameInfo.resultDetails != NULL) {
14920             free(gameInfo.resultDetails);
14921             gameInfo.resultDetails = NULL;
14922             gameInfo.result = GameUnfinished;
14923         }
14924         forwardMostMove = currentMove;
14925         HistorySet(parseList, backwardMostMove, forwardMostMove,
14926                    currentMove-1);
14927     }
14928 }
14929
14930 void
14931 HintEvent ()
14932 {
14933     if (appData.noChessProgram) return;
14934     switch (gameMode) {
14935       case MachinePlaysWhite:
14936         if (WhiteOnMove(forwardMostMove)) {
14937             DisplayError(_("Wait until your turn"), 0);
14938             return;
14939         }
14940         break;
14941       case BeginningOfGame:
14942       case MachinePlaysBlack:
14943         if (!WhiteOnMove(forwardMostMove)) {
14944             DisplayError(_("Wait until your turn"), 0);
14945             return;
14946         }
14947         break;
14948       default:
14949         DisplayError(_("No hint available"), 0);
14950         return;
14951     }
14952     SendToProgram("hint\n", &first);
14953     hintRequested = TRUE;
14954 }
14955
14956 void
14957 BookEvent ()
14958 {
14959     if (appData.noChessProgram) return;
14960     switch (gameMode) {
14961       case MachinePlaysWhite:
14962         if (WhiteOnMove(forwardMostMove)) {
14963             DisplayError(_("Wait until your turn"), 0);
14964             return;
14965         }
14966         break;
14967       case BeginningOfGame:
14968       case MachinePlaysBlack:
14969         if (!WhiteOnMove(forwardMostMove)) {
14970             DisplayError(_("Wait until your turn"), 0);
14971             return;
14972         }
14973         break;
14974       case EditPosition:
14975         EditPositionDone(TRUE);
14976         break;
14977       case TwoMachinesPlay:
14978         return;
14979       default:
14980         break;
14981     }
14982     SendToProgram("bk\n", &first);
14983     bookOutput[0] = NULLCHAR;
14984     bookRequested = TRUE;
14985 }
14986
14987 void
14988 AboutGameEvent ()
14989 {
14990     char *tags = PGNTags(&gameInfo);
14991     TagsPopUp(tags, CmailMsg());
14992     free(tags);
14993 }
14994
14995 /* end button procedures */
14996
14997 void
14998 PrintPosition (FILE *fp, int move)
14999 {
15000     int i, j;
15001
15002     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15003         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15004             char c = PieceToChar(boards[move][i][j]);
15005             fputc(c == 'x' ? '.' : c, fp);
15006             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15007         }
15008     }
15009     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15010       fprintf(fp, "white to play\n");
15011     else
15012       fprintf(fp, "black to play\n");
15013 }
15014
15015 void
15016 PrintOpponents (FILE *fp)
15017 {
15018     if (gameInfo.white != NULL) {
15019         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15020     } else {
15021         fprintf(fp, "\n");
15022     }
15023 }
15024
15025 /* Find last component of program's own name, using some heuristics */
15026 void
15027 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15028 {
15029     char *p, *q, c;
15030     int local = (strcmp(host, "localhost") == 0);
15031     while (!local && (p = strchr(prog, ';')) != NULL) {
15032         p++;
15033         while (*p == ' ') p++;
15034         prog = p;
15035     }
15036     if (*prog == '"' || *prog == '\'') {
15037         q = strchr(prog + 1, *prog);
15038     } else {
15039         q = strchr(prog, ' ');
15040     }
15041     if (q == NULL) q = prog + strlen(prog);
15042     p = q;
15043     while (p >= prog && *p != '/' && *p != '\\') p--;
15044     p++;
15045     if(p == prog && *p == '"') p++;
15046     c = *q; *q = 0;
15047     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15048     memcpy(buf, p, q - p);
15049     buf[q - p] = NULLCHAR;
15050     if (!local) {
15051         strcat(buf, "@");
15052         strcat(buf, host);
15053     }
15054 }
15055
15056 char *
15057 TimeControlTagValue ()
15058 {
15059     char buf[MSG_SIZ];
15060     if (!appData.clockMode) {
15061       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15062     } else if (movesPerSession > 0) {
15063       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15064     } else if (timeIncrement == 0) {
15065       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15066     } else {
15067       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15068     }
15069     return StrSave(buf);
15070 }
15071
15072 void
15073 SetGameInfo ()
15074 {
15075     /* This routine is used only for certain modes */
15076     VariantClass v = gameInfo.variant;
15077     ChessMove r = GameUnfinished;
15078     char *p = NULL;
15079
15080     if(keepInfo) return;
15081
15082     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15083         r = gameInfo.result;
15084         p = gameInfo.resultDetails;
15085         gameInfo.resultDetails = NULL;
15086     }
15087     ClearGameInfo(&gameInfo);
15088     gameInfo.variant = v;
15089
15090     switch (gameMode) {
15091       case MachinePlaysWhite:
15092         gameInfo.event = StrSave( appData.pgnEventHeader );
15093         gameInfo.site = StrSave(HostName());
15094         gameInfo.date = PGNDate();
15095         gameInfo.round = StrSave("-");
15096         gameInfo.white = StrSave(first.tidy);
15097         gameInfo.black = StrSave(UserName());
15098         gameInfo.timeControl = TimeControlTagValue();
15099         break;
15100
15101       case MachinePlaysBlack:
15102         gameInfo.event = StrSave( appData.pgnEventHeader );
15103         gameInfo.site = StrSave(HostName());
15104         gameInfo.date = PGNDate();
15105         gameInfo.round = StrSave("-");
15106         gameInfo.white = StrSave(UserName());
15107         gameInfo.black = StrSave(first.tidy);
15108         gameInfo.timeControl = TimeControlTagValue();
15109         break;
15110
15111       case TwoMachinesPlay:
15112         gameInfo.event = StrSave( appData.pgnEventHeader );
15113         gameInfo.site = StrSave(HostName());
15114         gameInfo.date = PGNDate();
15115         if (roundNr > 0) {
15116             char buf[MSG_SIZ];
15117             snprintf(buf, MSG_SIZ, "%d", roundNr);
15118             gameInfo.round = StrSave(buf);
15119         } else {
15120             gameInfo.round = StrSave("-");
15121         }
15122         if (first.twoMachinesColor[0] == 'w') {
15123             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15124             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15125         } else {
15126             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15127             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15128         }
15129         gameInfo.timeControl = TimeControlTagValue();
15130         break;
15131
15132       case EditGame:
15133         gameInfo.event = StrSave("Edited game");
15134         gameInfo.site = StrSave(HostName());
15135         gameInfo.date = PGNDate();
15136         gameInfo.round = StrSave("-");
15137         gameInfo.white = StrSave("-");
15138         gameInfo.black = StrSave("-");
15139         gameInfo.result = r;
15140         gameInfo.resultDetails = p;
15141         break;
15142
15143       case EditPosition:
15144         gameInfo.event = StrSave("Edited position");
15145         gameInfo.site = StrSave(HostName());
15146         gameInfo.date = PGNDate();
15147         gameInfo.round = StrSave("-");
15148         gameInfo.white = StrSave("-");
15149         gameInfo.black = StrSave("-");
15150         break;
15151
15152       case IcsPlayingWhite:
15153       case IcsPlayingBlack:
15154       case IcsObserving:
15155       case IcsExamining:
15156         break;
15157
15158       case PlayFromGameFile:
15159         gameInfo.event = StrSave("Game from non-PGN file");
15160         gameInfo.site = StrSave(HostName());
15161         gameInfo.date = PGNDate();
15162         gameInfo.round = StrSave("-");
15163         gameInfo.white = StrSave("?");
15164         gameInfo.black = StrSave("?");
15165         break;
15166
15167       default:
15168         break;
15169     }
15170 }
15171
15172 void
15173 ReplaceComment (int index, char *text)
15174 {
15175     int len;
15176     char *p;
15177     float score;
15178
15179     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15180        pvInfoList[index-1].depth == len &&
15181        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15182        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15183     while (*text == '\n') text++;
15184     len = strlen(text);
15185     while (len > 0 && text[len - 1] == '\n') len--;
15186
15187     if (commentList[index] != NULL)
15188       free(commentList[index]);
15189
15190     if (len == 0) {
15191         commentList[index] = NULL;
15192         return;
15193     }
15194   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15195       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15196       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15197     commentList[index] = (char *) malloc(len + 2);
15198     strncpy(commentList[index], text, len);
15199     commentList[index][len] = '\n';
15200     commentList[index][len + 1] = NULLCHAR;
15201   } else {
15202     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15203     char *p;
15204     commentList[index] = (char *) malloc(len + 7);
15205     safeStrCpy(commentList[index], "{\n", 3);
15206     safeStrCpy(commentList[index]+2, text, len+1);
15207     commentList[index][len+2] = NULLCHAR;
15208     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15209     strcat(commentList[index], "\n}\n");
15210   }
15211 }
15212
15213 void
15214 CrushCRs (char *text)
15215 {
15216   char *p = text;
15217   char *q = text;
15218   char ch;
15219
15220   do {
15221     ch = *p++;
15222     if (ch == '\r') continue;
15223     *q++ = ch;
15224   } while (ch != '\0');
15225 }
15226
15227 void
15228 AppendComment (int index, char *text, Boolean addBraces)
15229 /* addBraces  tells if we should add {} */
15230 {
15231     int oldlen, len;
15232     char *old;
15233
15234 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15235     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15236
15237     CrushCRs(text);
15238     while (*text == '\n') text++;
15239     len = strlen(text);
15240     while (len > 0 && text[len - 1] == '\n') len--;
15241     text[len] = NULLCHAR;
15242
15243     if (len == 0) return;
15244
15245     if (commentList[index] != NULL) {
15246       Boolean addClosingBrace = addBraces;
15247         old = commentList[index];
15248         oldlen = strlen(old);
15249         while(commentList[index][oldlen-1] ==  '\n')
15250           commentList[index][--oldlen] = NULLCHAR;
15251         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15252         safeStrCpy(commentList[index], old, oldlen + len + 6);
15253         free(old);
15254         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15255         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15256           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15257           while (*text == '\n') { text++; len--; }
15258           commentList[index][--oldlen] = NULLCHAR;
15259       }
15260         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15261         else          strcat(commentList[index], "\n");
15262         strcat(commentList[index], text);
15263         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15264         else          strcat(commentList[index], "\n");
15265     } else {
15266         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15267         if(addBraces)
15268           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15269         else commentList[index][0] = NULLCHAR;
15270         strcat(commentList[index], text);
15271         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15272         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15273     }
15274 }
15275
15276 static char *
15277 FindStr (char * text, char * sub_text)
15278 {
15279     char * result = strstr( text, sub_text );
15280
15281     if( result != NULL ) {
15282         result += strlen( sub_text );
15283     }
15284
15285     return result;
15286 }
15287
15288 /* [AS] Try to extract PV info from PGN comment */
15289 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15290 char *
15291 GetInfoFromComment (int index, char * text)
15292 {
15293     char * sep = text, *p;
15294
15295     if( text != NULL && index > 0 ) {
15296         int score = 0;
15297         int depth = 0;
15298         int time = -1, sec = 0, deci;
15299         char * s_eval = FindStr( text, "[%eval " );
15300         char * s_emt = FindStr( text, "[%emt " );
15301
15302         if( s_eval != NULL || s_emt != NULL ) {
15303             /* New style */
15304             char delim;
15305
15306             if( s_eval != NULL ) {
15307                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15308                     return text;
15309                 }
15310
15311                 if( delim != ']' ) {
15312                     return text;
15313                 }
15314             }
15315
15316             if( s_emt != NULL ) {
15317             }
15318                 return text;
15319         }
15320         else {
15321             /* We expect something like: [+|-]nnn.nn/dd */
15322             int score_lo = 0;
15323
15324             if(*text != '{') return text; // [HGM] braces: must be normal comment
15325
15326             sep = strchr( text, '/' );
15327             if( sep == NULL || sep < (text+4) ) {
15328                 return text;
15329             }
15330
15331             p = text;
15332             if(p[1] == '(') { // comment starts with PV
15333                p = strchr(p, ')'); // locate end of PV
15334                if(p == NULL || sep < p+5) return text;
15335                // at this point we have something like "{(.*) +0.23/6 ..."
15336                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15337                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15338                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15339             }
15340             time = -1; sec = -1; deci = -1;
15341             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15342                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15343                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15344                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15345                 return text;
15346             }
15347
15348             if( score_lo < 0 || score_lo >= 100 ) {
15349                 return text;
15350             }
15351
15352             if(sec >= 0) time = 600*time + 10*sec; else
15353             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15354
15355             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15356
15357             /* [HGM] PV time: now locate end of PV info */
15358             while( *++sep >= '0' && *sep <= '9'); // strip depth
15359             if(time >= 0)
15360             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15361             if(sec >= 0)
15362             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15363             if(deci >= 0)
15364             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15365             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15366         }
15367
15368         if( depth <= 0 ) {
15369             return text;
15370         }
15371
15372         if( time < 0 ) {
15373             time = -1;
15374         }
15375
15376         pvInfoList[index-1].depth = depth;
15377         pvInfoList[index-1].score = score;
15378         pvInfoList[index-1].time  = 10*time; // centi-sec
15379         if(*sep == '}') *sep = 0; else *--sep = '{';
15380         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15381     }
15382     return sep;
15383 }
15384
15385 void
15386 SendToProgram (char *message, ChessProgramState *cps)
15387 {
15388     int count, outCount, error;
15389     char buf[MSG_SIZ];
15390
15391     if (cps->pr == NoProc) return;
15392     Attention(cps);
15393
15394     if (appData.debugMode) {
15395         TimeMark now;
15396         GetTimeMark(&now);
15397         fprintf(debugFP, "%ld >%-6s: %s",
15398                 SubtractTimeMarks(&now, &programStartTime),
15399                 cps->which, message);
15400         if(serverFP)
15401             fprintf(serverFP, "%ld >%-6s: %s",
15402                 SubtractTimeMarks(&now, &programStartTime),
15403                 cps->which, message), fflush(serverFP);
15404     }
15405
15406     count = strlen(message);
15407     outCount = OutputToProcess(cps->pr, message, count, &error);
15408     if (outCount < count && !exiting
15409                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15410       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15411       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15412         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15413             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15414                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15415                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15416                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15417             } else {
15418                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15419                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15420                 gameInfo.result = res;
15421             }
15422             gameInfo.resultDetails = StrSave(buf);
15423         }
15424         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15425         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15426     }
15427 }
15428
15429 void
15430 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15431 {
15432     char *end_str;
15433     char buf[MSG_SIZ];
15434     ChessProgramState *cps = (ChessProgramState *)closure;
15435
15436     if (isr != cps->isr) return; /* Killed intentionally */
15437     if (count <= 0) {
15438         if (count == 0) {
15439             RemoveInputSource(cps->isr);
15440             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15441                     _(cps->which), cps->program);
15442             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15443             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15444                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15445                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15446                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15447                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15448                 } else {
15449                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15450                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15451                     gameInfo.result = res;
15452                 }
15453                 gameInfo.resultDetails = StrSave(buf);
15454             }
15455             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15456             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15457         } else {
15458             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15459                     _(cps->which), cps->program);
15460             RemoveInputSource(cps->isr);
15461
15462             /* [AS] Program is misbehaving badly... kill it */
15463             if( count == -2 ) {
15464                 DestroyChildProcess( cps->pr, 9 );
15465                 cps->pr = NoProc;
15466             }
15467
15468             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15469         }
15470         return;
15471     }
15472
15473     if ((end_str = strchr(message, '\r')) != NULL)
15474       *end_str = NULLCHAR;
15475     if ((end_str = strchr(message, '\n')) != NULL)
15476       *end_str = NULLCHAR;
15477
15478     if (appData.debugMode) {
15479         TimeMark now; int print = 1;
15480         char *quote = ""; char c; int i;
15481
15482         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15483                 char start = message[0];
15484                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15485                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15486                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15487                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15488                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15489                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15490                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15491                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15492                    sscanf(message, "hint: %c", &c)!=1 && 
15493                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15494                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15495                     print = (appData.engineComments >= 2);
15496                 }
15497                 message[0] = start; // restore original message
15498         }
15499         if(print) {
15500                 GetTimeMark(&now);
15501                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15502                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15503                         quote,
15504                         message);
15505                 if(serverFP)
15506                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15507                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15508                         quote,
15509                         message), fflush(serverFP);
15510         }
15511     }
15512
15513     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15514     if (appData.icsEngineAnalyze) {
15515         if (strstr(message, "whisper") != NULL ||
15516              strstr(message, "kibitz") != NULL ||
15517             strstr(message, "tellics") != NULL) return;
15518     }
15519
15520     HandleMachineMove(message, cps);
15521 }
15522
15523
15524 void
15525 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15526 {
15527     char buf[MSG_SIZ];
15528     int seconds;
15529
15530     if( timeControl_2 > 0 ) {
15531         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15532             tc = timeControl_2;
15533         }
15534     }
15535     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15536     inc /= cps->timeOdds;
15537     st  /= cps->timeOdds;
15538
15539     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15540
15541     if (st > 0) {
15542       /* Set exact time per move, normally using st command */
15543       if (cps->stKludge) {
15544         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15545         seconds = st % 60;
15546         if (seconds == 0) {
15547           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15548         } else {
15549           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15550         }
15551       } else {
15552         snprintf(buf, MSG_SIZ, "st %d\n", st);
15553       }
15554     } else {
15555       /* Set conventional or incremental time control, using level command */
15556       if (seconds == 0) {
15557         /* Note old gnuchess bug -- minutes:seconds used to not work.
15558            Fixed in later versions, but still avoid :seconds
15559            when seconds is 0. */
15560         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15561       } else {
15562         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15563                  seconds, inc/1000.);
15564       }
15565     }
15566     SendToProgram(buf, cps);
15567
15568     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15569     /* Orthogonally, limit search to given depth */
15570     if (sd > 0) {
15571       if (cps->sdKludge) {
15572         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15573       } else {
15574         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15575       }
15576       SendToProgram(buf, cps);
15577     }
15578
15579     if(cps->nps >= 0) { /* [HGM] nps */
15580         if(cps->supportsNPS == FALSE)
15581           cps->nps = -1; // don't use if engine explicitly says not supported!
15582         else {
15583           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15584           SendToProgram(buf, cps);
15585         }
15586     }
15587 }
15588
15589 ChessProgramState *
15590 WhitePlayer ()
15591 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15592 {
15593     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15594        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15595         return &second;
15596     return &first;
15597 }
15598
15599 void
15600 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15601 {
15602     char message[MSG_SIZ];
15603     long time, otime;
15604
15605     /* Note: this routine must be called when the clocks are stopped
15606        or when they have *just* been set or switched; otherwise
15607        it will be off by the time since the current tick started.
15608     */
15609     if (machineWhite) {
15610         time = whiteTimeRemaining / 10;
15611         otime = blackTimeRemaining / 10;
15612     } else {
15613         time = blackTimeRemaining / 10;
15614         otime = whiteTimeRemaining / 10;
15615     }
15616     /* [HGM] translate opponent's time by time-odds factor */
15617     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15618
15619     if (time <= 0) time = 1;
15620     if (otime <= 0) otime = 1;
15621
15622     snprintf(message, MSG_SIZ, "time %ld\n", time);
15623     SendToProgram(message, cps);
15624
15625     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15626     SendToProgram(message, cps);
15627 }
15628
15629 int
15630 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15631 {
15632   char buf[MSG_SIZ];
15633   int len = strlen(name);
15634   int val;
15635
15636   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15637     (*p) += len + 1;
15638     sscanf(*p, "%d", &val);
15639     *loc = (val != 0);
15640     while (**p && **p != ' ')
15641       (*p)++;
15642     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15643     SendToProgram(buf, cps);
15644     return TRUE;
15645   }
15646   return FALSE;
15647 }
15648
15649 int
15650 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15651 {
15652   char buf[MSG_SIZ];
15653   int len = strlen(name);
15654   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15655     (*p) += len + 1;
15656     sscanf(*p, "%d", loc);
15657     while (**p && **p != ' ') (*p)++;
15658     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15659     SendToProgram(buf, cps);
15660     return TRUE;
15661   }
15662   return FALSE;
15663 }
15664
15665 int
15666 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15667 {
15668   char buf[MSG_SIZ];
15669   int len = strlen(name);
15670   if (strncmp((*p), name, len) == 0
15671       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15672     (*p) += len + 2;
15673     sscanf(*p, "%[^\"]", loc);
15674     while (**p && **p != '\"') (*p)++;
15675     if (**p == '\"') (*p)++;
15676     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15677     SendToProgram(buf, cps);
15678     return TRUE;
15679   }
15680   return FALSE;
15681 }
15682
15683 int
15684 ParseOption (Option *opt, ChessProgramState *cps)
15685 // [HGM] options: process the string that defines an engine option, and determine
15686 // name, type, default value, and allowed value range
15687 {
15688         char *p, *q, buf[MSG_SIZ];
15689         int n, min = (-1)<<31, max = 1<<31, def;
15690
15691         if(p = strstr(opt->name, " -spin ")) {
15692             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15693             if(max < min) max = min; // enforce consistency
15694             if(def < min) def = min;
15695             if(def > max) def = max;
15696             opt->value = def;
15697             opt->min = min;
15698             opt->max = max;
15699             opt->type = Spin;
15700         } else if((p = strstr(opt->name, " -slider "))) {
15701             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15702             if((n = sscanf(p, " -slider %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; // Slider;
15710         } else if((p = strstr(opt->name, " -string "))) {
15711             opt->textValue = p+9;
15712             opt->type = TextBox;
15713         } else if((p = strstr(opt->name, " -file "))) {
15714             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15715             opt->textValue = p+7;
15716             opt->type = FileName; // FileName;
15717         } else if((p = strstr(opt->name, " -path "))) {
15718             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15719             opt->textValue = p+7;
15720             opt->type = PathName; // PathName;
15721         } else if(p = strstr(opt->name, " -check ")) {
15722             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15723             opt->value = (def != 0);
15724             opt->type = CheckBox;
15725         } else if(p = strstr(opt->name, " -combo ")) {
15726             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15727             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15728             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15729             opt->value = n = 0;
15730             while(q = StrStr(q, " /// ")) {
15731                 n++; *q = 0;    // count choices, and null-terminate each of them
15732                 q += 5;
15733                 if(*q == '*') { // remember default, which is marked with * prefix
15734                     q++;
15735                     opt->value = n;
15736                 }
15737                 cps->comboList[cps->comboCnt++] = q;
15738             }
15739             cps->comboList[cps->comboCnt++] = NULL;
15740             opt->max = n + 1;
15741             opt->type = ComboBox;
15742         } else if(p = strstr(opt->name, " -button")) {
15743             opt->type = Button;
15744         } else if(p = strstr(opt->name, " -save")) {
15745             opt->type = SaveButton;
15746         } else return FALSE;
15747         *p = 0; // terminate option name
15748         // now look if the command-line options define a setting for this engine option.
15749         if(cps->optionSettings && cps->optionSettings[0])
15750             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15751         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15752           snprintf(buf, MSG_SIZ, "option %s", p);
15753                 if(p = strstr(buf, ",")) *p = 0;
15754                 if(q = strchr(buf, '=')) switch(opt->type) {
15755                     case ComboBox:
15756                         for(n=0; n<opt->max; n++)
15757                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15758                         break;
15759                     case TextBox:
15760                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15761                         break;
15762                     case Spin:
15763                     case CheckBox:
15764                         opt->value = atoi(q+1);
15765                     default:
15766                         break;
15767                 }
15768                 strcat(buf, "\n");
15769                 SendToProgram(buf, cps);
15770         }
15771         return TRUE;
15772 }
15773
15774 void
15775 FeatureDone (ChessProgramState *cps, int val)
15776 {
15777   DelayedEventCallback cb = GetDelayedEvent();
15778   if ((cb == InitBackEnd3 && cps == &first) ||
15779       (cb == SettingsMenuIfReady && cps == &second) ||
15780       (cb == LoadEngine) ||
15781       (cb == TwoMachinesEventIfReady)) {
15782     CancelDelayedEvent();
15783     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15784   }
15785   cps->initDone = val;
15786 }
15787
15788 /* Parse feature command from engine */
15789 void
15790 ParseFeatures (char *args, ChessProgramState *cps)
15791 {
15792   char *p = args;
15793   char *q;
15794   int val;
15795   char buf[MSG_SIZ];
15796
15797   for (;;) {
15798     while (*p == ' ') p++;
15799     if (*p == NULLCHAR) return;
15800
15801     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15802     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15803     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15804     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15805     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15806     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15807     if (BoolFeature(&p, "reuse", &val, cps)) {
15808       /* Engine can disable reuse, but can't enable it if user said no */
15809       if (!val) cps->reuse = FALSE;
15810       continue;
15811     }
15812     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15813     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15814       if (gameMode == TwoMachinesPlay) {
15815         DisplayTwoMachinesTitle();
15816       } else {
15817         DisplayTitle("");
15818       }
15819       continue;
15820     }
15821     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15822     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15823     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15824     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15825     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15826     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15827     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15828     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15829     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15830     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15831     if (IntFeature(&p, "done", &val, cps)) {
15832       FeatureDone(cps, val);
15833       continue;
15834     }
15835     /* Added by Tord: */
15836     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15837     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15838     /* End of additions by Tord */
15839
15840     /* [HGM] added features: */
15841     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15842     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15843     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15844     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15845     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15846     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15847     if (StringFeature(&p, "option", buf, cps)) {
15848         FREE(cps->option[cps->nrOptions].name);
15849         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15850         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15851         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15852           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15853             SendToProgram(buf, cps);
15854             continue;
15855         }
15856         if(cps->nrOptions >= MAX_OPTIONS) {
15857             cps->nrOptions--;
15858             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15859             DisplayError(buf, 0);
15860         }
15861         continue;
15862     }
15863     /* End of additions by HGM */
15864
15865     /* unknown feature: complain and skip */
15866     q = p;
15867     while (*q && *q != '=') q++;
15868     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15869     SendToProgram(buf, cps);
15870     p = q;
15871     if (*p == '=') {
15872       p++;
15873       if (*p == '\"') {
15874         p++;
15875         while (*p && *p != '\"') p++;
15876         if (*p == '\"') p++;
15877       } else {
15878         while (*p && *p != ' ') p++;
15879       }
15880     }
15881   }
15882
15883 }
15884
15885 void
15886 PeriodicUpdatesEvent (int newState)
15887 {
15888     if (newState == appData.periodicUpdates)
15889       return;
15890
15891     appData.periodicUpdates=newState;
15892
15893     /* Display type changes, so update it now */
15894 //    DisplayAnalysis();
15895
15896     /* Get the ball rolling again... */
15897     if (newState) {
15898         AnalysisPeriodicEvent(1);
15899         StartAnalysisClock();
15900     }
15901 }
15902
15903 void
15904 PonderNextMoveEvent (int newState)
15905 {
15906     if (newState == appData.ponderNextMove) return;
15907     if (gameMode == EditPosition) EditPositionDone(TRUE);
15908     if (newState) {
15909         SendToProgram("hard\n", &first);
15910         if (gameMode == TwoMachinesPlay) {
15911             SendToProgram("hard\n", &second);
15912         }
15913     } else {
15914         SendToProgram("easy\n", &first);
15915         thinkOutput[0] = NULLCHAR;
15916         if (gameMode == TwoMachinesPlay) {
15917             SendToProgram("easy\n", &second);
15918         }
15919     }
15920     appData.ponderNextMove = newState;
15921 }
15922
15923 void
15924 NewSettingEvent (int option, int *feature, char *command, int value)
15925 {
15926     char buf[MSG_SIZ];
15927
15928     if (gameMode == EditPosition) EditPositionDone(TRUE);
15929     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15930     if(feature == NULL || *feature) SendToProgram(buf, &first);
15931     if (gameMode == TwoMachinesPlay) {
15932         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15933     }
15934 }
15935
15936 void
15937 ShowThinkingEvent ()
15938 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15939 {
15940     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15941     int newState = appData.showThinking
15942         // [HGM] thinking: other features now need thinking output as well
15943         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15944
15945     if (oldState == newState) return;
15946     oldState = newState;
15947     if (gameMode == EditPosition) EditPositionDone(TRUE);
15948     if (oldState) {
15949         SendToProgram("post\n", &first);
15950         if (gameMode == TwoMachinesPlay) {
15951             SendToProgram("post\n", &second);
15952         }
15953     } else {
15954         SendToProgram("nopost\n", &first);
15955         thinkOutput[0] = NULLCHAR;
15956         if (gameMode == TwoMachinesPlay) {
15957             SendToProgram("nopost\n", &second);
15958         }
15959     }
15960 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15961 }
15962
15963 void
15964 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15965 {
15966   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15967   if (pr == NoProc) return;
15968   AskQuestion(title, question, replyPrefix, pr);
15969 }
15970
15971 void
15972 TypeInEvent (char firstChar)
15973 {
15974     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15975         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15976         gameMode == AnalyzeMode || gameMode == EditGame || 
15977         gameMode == EditPosition || gameMode == IcsExamining ||
15978         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15979         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15980                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15981                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15982         gameMode == Training) PopUpMoveDialog(firstChar);
15983 }
15984
15985 void
15986 TypeInDoneEvent (char *move)
15987 {
15988         Board board;
15989         int n, fromX, fromY, toX, toY;
15990         char promoChar;
15991         ChessMove moveType;
15992
15993         // [HGM] FENedit
15994         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15995                 EditPositionPasteFEN(move);
15996                 return;
15997         }
15998         // [HGM] movenum: allow move number to be typed in any mode
15999         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16000           ToNrEvent(2*n-1);
16001           return;
16002         }
16003         // undocumented kludge: allow command-line option to be typed in!
16004         // (potentially fatal, and does not implement the effect of the option.)
16005         // should only be used for options that are values on which future decisions will be made,
16006         // and definitely not on options that would be used during initialization.
16007         if(strstr(move, "!!! -") == move) {
16008             ParseArgsFromString(move+4);
16009             return;
16010         }
16011
16012       if (gameMode != EditGame && currentMove != forwardMostMove && 
16013         gameMode != Training) {
16014         DisplayMoveError(_("Displayed move is not current"));
16015       } else {
16016         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16017           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16018         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16019         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
16020           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16021           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
16022         } else {
16023           DisplayMoveError(_("Could not parse move"));
16024         }
16025       }
16026 }
16027
16028 void
16029 DisplayMove (int moveNumber)
16030 {
16031     char message[MSG_SIZ];
16032     char res[MSG_SIZ];
16033     char cpThinkOutput[MSG_SIZ];
16034
16035     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16036
16037     if (moveNumber == forwardMostMove - 1 ||
16038         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16039
16040         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16041
16042         if (strchr(cpThinkOutput, '\n')) {
16043             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16044         }
16045     } else {
16046         *cpThinkOutput = NULLCHAR;
16047     }
16048
16049     /* [AS] Hide thinking from human user */
16050     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16051         *cpThinkOutput = NULLCHAR;
16052         if( thinkOutput[0] != NULLCHAR ) {
16053             int i;
16054
16055             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16056                 cpThinkOutput[i] = '.';
16057             }
16058             cpThinkOutput[i] = NULLCHAR;
16059             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16060         }
16061     }
16062
16063     if (moveNumber == forwardMostMove - 1 &&
16064         gameInfo.resultDetails != NULL) {
16065         if (gameInfo.resultDetails[0] == NULLCHAR) {
16066           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16067         } else {
16068           snprintf(res, MSG_SIZ, " {%s} %s",
16069                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16070         }
16071     } else {
16072         res[0] = NULLCHAR;
16073     }
16074
16075     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16076         DisplayMessage(res, cpThinkOutput);
16077     } else {
16078       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16079                 WhiteOnMove(moveNumber) ? " " : ".. ",
16080                 parseList[moveNumber], res);
16081         DisplayMessage(message, cpThinkOutput);
16082     }
16083 }
16084
16085 void
16086 DisplayComment (int moveNumber, char *text)
16087 {
16088     char title[MSG_SIZ];
16089
16090     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16091       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16092     } else {
16093       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16094               WhiteOnMove(moveNumber) ? " " : ".. ",
16095               parseList[moveNumber]);
16096     }
16097     if (text != NULL && (appData.autoDisplayComment || commentUp))
16098         CommentPopUp(title, text);
16099 }
16100
16101 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16102  * might be busy thinking or pondering.  It can be omitted if your
16103  * gnuchess is configured to stop thinking immediately on any user
16104  * input.  However, that gnuchess feature depends on the FIONREAD
16105  * ioctl, which does not work properly on some flavors of Unix.
16106  */
16107 void
16108 Attention (ChessProgramState *cps)
16109 {
16110 #if ATTENTION
16111     if (!cps->useSigint) return;
16112     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16113     switch (gameMode) {
16114       case MachinePlaysWhite:
16115       case MachinePlaysBlack:
16116       case TwoMachinesPlay:
16117       case IcsPlayingWhite:
16118       case IcsPlayingBlack:
16119       case AnalyzeMode:
16120       case AnalyzeFile:
16121         /* Skip if we know it isn't thinking */
16122         if (!cps->maybeThinking) return;
16123         if (appData.debugMode)
16124           fprintf(debugFP, "Interrupting %s\n", cps->which);
16125         InterruptChildProcess(cps->pr);
16126         cps->maybeThinking = FALSE;
16127         break;
16128       default:
16129         break;
16130     }
16131 #endif /*ATTENTION*/
16132 }
16133
16134 int
16135 CheckFlags ()
16136 {
16137     if (whiteTimeRemaining <= 0) {
16138         if (!whiteFlag) {
16139             whiteFlag = TRUE;
16140             if (appData.icsActive) {
16141                 if (appData.autoCallFlag &&
16142                     gameMode == IcsPlayingBlack && !blackFlag) {
16143                   SendToICS(ics_prefix);
16144                   SendToICS("flag\n");
16145                 }
16146             } else {
16147                 if (blackFlag) {
16148                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16149                 } else {
16150                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16151                     if (appData.autoCallFlag) {
16152                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16153                         return TRUE;
16154                     }
16155                 }
16156             }
16157         }
16158     }
16159     if (blackTimeRemaining <= 0) {
16160         if (!blackFlag) {
16161             blackFlag = TRUE;
16162             if (appData.icsActive) {
16163                 if (appData.autoCallFlag &&
16164                     gameMode == IcsPlayingWhite && !whiteFlag) {
16165                   SendToICS(ics_prefix);
16166                   SendToICS("flag\n");
16167                 }
16168             } else {
16169                 if (whiteFlag) {
16170                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16171                 } else {
16172                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16173                     if (appData.autoCallFlag) {
16174                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16175                         return TRUE;
16176                     }
16177                 }
16178             }
16179         }
16180     }
16181     return FALSE;
16182 }
16183
16184 void
16185 CheckTimeControl ()
16186 {
16187     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16188         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16189
16190     /*
16191      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16192      */
16193     if ( !WhiteOnMove(forwardMostMove) ) {
16194         /* White made time control */
16195         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16196         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16197         /* [HGM] time odds: correct new time quota for time odds! */
16198                                             / WhitePlayer()->timeOdds;
16199         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16200     } else {
16201         lastBlack -= blackTimeRemaining;
16202         /* Black made time control */
16203         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16204                                             / WhitePlayer()->other->timeOdds;
16205         lastWhite = whiteTimeRemaining;
16206     }
16207 }
16208
16209 void
16210 DisplayBothClocks ()
16211 {
16212     int wom = gameMode == EditPosition ?
16213       !blackPlaysFirst : WhiteOnMove(currentMove);
16214     DisplayWhiteClock(whiteTimeRemaining, wom);
16215     DisplayBlackClock(blackTimeRemaining, !wom);
16216 }
16217
16218
16219 /* Timekeeping seems to be a portability nightmare.  I think everyone
16220    has ftime(), but I'm really not sure, so I'm including some ifdefs
16221    to use other calls if you don't.  Clocks will be less accurate if
16222    you have neither ftime nor gettimeofday.
16223 */
16224
16225 /* VS 2008 requires the #include outside of the function */
16226 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16227 #include <sys/timeb.h>
16228 #endif
16229
16230 /* Get the current time as a TimeMark */
16231 void
16232 GetTimeMark (TimeMark *tm)
16233 {
16234 #if HAVE_GETTIMEOFDAY
16235
16236     struct timeval timeVal;
16237     struct timezone timeZone;
16238
16239     gettimeofday(&timeVal, &timeZone);
16240     tm->sec = (long) timeVal.tv_sec;
16241     tm->ms = (int) (timeVal.tv_usec / 1000L);
16242
16243 #else /*!HAVE_GETTIMEOFDAY*/
16244 #if HAVE_FTIME
16245
16246 // include <sys/timeb.h> / moved to just above start of function
16247     struct timeb timeB;
16248
16249     ftime(&timeB);
16250     tm->sec = (long) timeB.time;
16251     tm->ms = (int) timeB.millitm;
16252
16253 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16254     tm->sec = (long) time(NULL);
16255     tm->ms = 0;
16256 #endif
16257 #endif
16258 }
16259
16260 /* Return the difference in milliseconds between two
16261    time marks.  We assume the difference will fit in a long!
16262 */
16263 long
16264 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16265 {
16266     return 1000L*(tm2->sec - tm1->sec) +
16267            (long) (tm2->ms - tm1->ms);
16268 }
16269
16270
16271 /*
16272  * Code to manage the game clocks.
16273  *
16274  * In tournament play, black starts the clock and then white makes a move.
16275  * We give the human user a slight advantage if he is playing white---the
16276  * clocks don't run until he makes his first move, so it takes zero time.
16277  * Also, we don't account for network lag, so we could get out of sync
16278  * with GNU Chess's clock -- but then, referees are always right.
16279  */
16280
16281 static TimeMark tickStartTM;
16282 static long intendedTickLength;
16283
16284 long
16285 NextTickLength (long timeRemaining)
16286 {
16287     long nominalTickLength, nextTickLength;
16288
16289     if (timeRemaining > 0L && timeRemaining <= 10000L)
16290       nominalTickLength = 100L;
16291     else
16292       nominalTickLength = 1000L;
16293     nextTickLength = timeRemaining % nominalTickLength;
16294     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16295
16296     return nextTickLength;
16297 }
16298
16299 /* Adjust clock one minute up or down */
16300 void
16301 AdjustClock (Boolean which, int dir)
16302 {
16303     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16304     if(which) blackTimeRemaining += 60000*dir;
16305     else      whiteTimeRemaining += 60000*dir;
16306     DisplayBothClocks();
16307     adjustedClock = TRUE;
16308 }
16309
16310 /* Stop clocks and reset to a fresh time control */
16311 void
16312 ResetClocks ()
16313 {
16314     (void) StopClockTimer();
16315     if (appData.icsActive) {
16316         whiteTimeRemaining = blackTimeRemaining = 0;
16317     } else if (searchTime) {
16318         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16319         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16320     } else { /* [HGM] correct new time quote for time odds */
16321         whiteTC = blackTC = fullTimeControlString;
16322         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16323         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16324     }
16325     if (whiteFlag || blackFlag) {
16326         DisplayTitle("");
16327         whiteFlag = blackFlag = FALSE;
16328     }
16329     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16330     DisplayBothClocks();
16331     adjustedClock = FALSE;
16332 }
16333
16334 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16335
16336 /* Decrement running clock by amount of time that has passed */
16337 void
16338 DecrementClocks ()
16339 {
16340     long timeRemaining;
16341     long lastTickLength, fudge;
16342     TimeMark now;
16343
16344     if (!appData.clockMode) return;
16345     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16346
16347     GetTimeMark(&now);
16348
16349     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16350
16351     /* Fudge if we woke up a little too soon */
16352     fudge = intendedTickLength - lastTickLength;
16353     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16354
16355     if (WhiteOnMove(forwardMostMove)) {
16356         if(whiteNPS >= 0) lastTickLength = 0;
16357         timeRemaining = whiteTimeRemaining -= lastTickLength;
16358         if(timeRemaining < 0 && !appData.icsActive) {
16359             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16360             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16361                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16362                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16363             }
16364         }
16365         DisplayWhiteClock(whiteTimeRemaining - fudge,
16366                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16367     } else {
16368         if(blackNPS >= 0) lastTickLength = 0;
16369         timeRemaining = blackTimeRemaining -= lastTickLength;
16370         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16371             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16372             if(suddenDeath) {
16373                 blackStartMove = forwardMostMove;
16374                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16375             }
16376         }
16377         DisplayBlackClock(blackTimeRemaining - fudge,
16378                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16379     }
16380     if (CheckFlags()) return;
16381
16382     if(twoBoards) { // count down secondary board's clocks as well
16383         activePartnerTime -= lastTickLength;
16384         partnerUp = 1;
16385         if(activePartner == 'W')
16386             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16387         else
16388             DisplayBlackClock(activePartnerTime, TRUE);
16389         partnerUp = 0;
16390     }
16391
16392     tickStartTM = now;
16393     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16394     StartClockTimer(intendedTickLength);
16395
16396     /* if the time remaining has fallen below the alarm threshold, sound the
16397      * alarm. if the alarm has sounded and (due to a takeback or time control
16398      * with increment) the time remaining has increased to a level above the
16399      * threshold, reset the alarm so it can sound again.
16400      */
16401
16402     if (appData.icsActive && appData.icsAlarm) {
16403
16404         /* make sure we are dealing with the user's clock */
16405         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16406                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16407            )) return;
16408
16409         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16410             alarmSounded = FALSE;
16411         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16412             PlayAlarmSound();
16413             alarmSounded = TRUE;
16414         }
16415     }
16416 }
16417
16418
16419 /* A player has just moved, so stop the previously running
16420    clock and (if in clock mode) start the other one.
16421    We redisplay both clocks in case we're in ICS mode, because
16422    ICS gives us an update to both clocks after every move.
16423    Note that this routine is called *after* forwardMostMove
16424    is updated, so the last fractional tick must be subtracted
16425    from the color that is *not* on move now.
16426 */
16427 void
16428 SwitchClocks (int newMoveNr)
16429 {
16430     long lastTickLength;
16431     TimeMark now;
16432     int flagged = FALSE;
16433
16434     GetTimeMark(&now);
16435
16436     if (StopClockTimer() && appData.clockMode) {
16437         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16438         if (!WhiteOnMove(forwardMostMove)) {
16439             if(blackNPS >= 0) lastTickLength = 0;
16440             blackTimeRemaining -= lastTickLength;
16441            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16442 //         if(pvInfoList[forwardMostMove].time == -1)
16443                  pvInfoList[forwardMostMove].time =               // use GUI time
16444                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16445         } else {
16446            if(whiteNPS >= 0) lastTickLength = 0;
16447            whiteTimeRemaining -= lastTickLength;
16448            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16449 //         if(pvInfoList[forwardMostMove].time == -1)
16450                  pvInfoList[forwardMostMove].time =
16451                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16452         }
16453         flagged = CheckFlags();
16454     }
16455     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16456     CheckTimeControl();
16457
16458     if (flagged || !appData.clockMode) return;
16459
16460     switch (gameMode) {
16461       case MachinePlaysBlack:
16462       case MachinePlaysWhite:
16463       case BeginningOfGame:
16464         if (pausing) return;
16465         break;
16466
16467       case EditGame:
16468       case PlayFromGameFile:
16469       case IcsExamining:
16470         return;
16471
16472       default:
16473         break;
16474     }
16475
16476     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16477         if(WhiteOnMove(forwardMostMove))
16478              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16479         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16480     }
16481
16482     tickStartTM = now;
16483     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16484       whiteTimeRemaining : blackTimeRemaining);
16485     StartClockTimer(intendedTickLength);
16486 }
16487
16488
16489 /* Stop both clocks */
16490 void
16491 StopClocks ()
16492 {
16493     long lastTickLength;
16494     TimeMark now;
16495
16496     if (!StopClockTimer()) return;
16497     if (!appData.clockMode) return;
16498
16499     GetTimeMark(&now);
16500
16501     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16502     if (WhiteOnMove(forwardMostMove)) {
16503         if(whiteNPS >= 0) lastTickLength = 0;
16504         whiteTimeRemaining -= lastTickLength;
16505         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16506     } else {
16507         if(blackNPS >= 0) lastTickLength = 0;
16508         blackTimeRemaining -= lastTickLength;
16509         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16510     }
16511     CheckFlags();
16512 }
16513
16514 /* Start clock of player on move.  Time may have been reset, so
16515    if clock is already running, stop and restart it. */
16516 void
16517 StartClocks ()
16518 {
16519     (void) StopClockTimer(); /* in case it was running already */
16520     DisplayBothClocks();
16521     if (CheckFlags()) return;
16522
16523     if (!appData.clockMode) return;
16524     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16525
16526     GetTimeMark(&tickStartTM);
16527     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16528       whiteTimeRemaining : blackTimeRemaining);
16529
16530    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16531     whiteNPS = blackNPS = -1;
16532     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16533        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16534         whiteNPS = first.nps;
16535     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16536        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16537         blackNPS = first.nps;
16538     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16539         whiteNPS = second.nps;
16540     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16541         blackNPS = second.nps;
16542     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16543
16544     StartClockTimer(intendedTickLength);
16545 }
16546
16547 char *
16548 TimeString (long ms)
16549 {
16550     long second, minute, hour, day;
16551     char *sign = "";
16552     static char buf[32];
16553
16554     if (ms > 0 && ms <= 9900) {
16555       /* convert milliseconds to tenths, rounding up */
16556       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16557
16558       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16559       return buf;
16560     }
16561
16562     /* convert milliseconds to seconds, rounding up */
16563     /* use floating point to avoid strangeness of integer division
16564        with negative dividends on many machines */
16565     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16566
16567     if (second < 0) {
16568         sign = "-";
16569         second = -second;
16570     }
16571
16572     day = second / (60 * 60 * 24);
16573     second = second % (60 * 60 * 24);
16574     hour = second / (60 * 60);
16575     second = second % (60 * 60);
16576     minute = second / 60;
16577     second = second % 60;
16578
16579     if (day > 0)
16580       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16581               sign, day, hour, minute, second);
16582     else if (hour > 0)
16583       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16584     else
16585       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16586
16587     return buf;
16588 }
16589
16590
16591 /*
16592  * This is necessary because some C libraries aren't ANSI C compliant yet.
16593  */
16594 char *
16595 StrStr (char *string, char *match)
16596 {
16597     int i, length;
16598
16599     length = strlen(match);
16600
16601     for (i = strlen(string) - length; i >= 0; i--, string++)
16602       if (!strncmp(match, string, length))
16603         return string;
16604
16605     return NULL;
16606 }
16607
16608 char *
16609 StrCaseStr (char *string, char *match)
16610 {
16611     int i, j, length;
16612
16613     length = strlen(match);
16614
16615     for (i = strlen(string) - length; i >= 0; i--, string++) {
16616         for (j = 0; j < length; j++) {
16617             if (ToLower(match[j]) != ToLower(string[j]))
16618               break;
16619         }
16620         if (j == length) return string;
16621     }
16622
16623     return NULL;
16624 }
16625
16626 #ifndef _amigados
16627 int
16628 StrCaseCmp (char *s1, char *s2)
16629 {
16630     char c1, c2;
16631
16632     for (;;) {
16633         c1 = ToLower(*s1++);
16634         c2 = ToLower(*s2++);
16635         if (c1 > c2) return 1;
16636         if (c1 < c2) return -1;
16637         if (c1 == NULLCHAR) return 0;
16638     }
16639 }
16640
16641
16642 int
16643 ToLower (int c)
16644 {
16645     return isupper(c) ? tolower(c) : c;
16646 }
16647
16648
16649 int
16650 ToUpper (int c)
16651 {
16652     return islower(c) ? toupper(c) : c;
16653 }
16654 #endif /* !_amigados    */
16655
16656 char *
16657 StrSave (char *s)
16658 {
16659   char *ret;
16660
16661   if ((ret = (char *) malloc(strlen(s) + 1)))
16662     {
16663       safeStrCpy(ret, s, strlen(s)+1);
16664     }
16665   return ret;
16666 }
16667
16668 char *
16669 StrSavePtr (char *s, char **savePtr)
16670 {
16671     if (*savePtr) {
16672         free(*savePtr);
16673     }
16674     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16675       safeStrCpy(*savePtr, s, strlen(s)+1);
16676     }
16677     return(*savePtr);
16678 }
16679
16680 char *
16681 PGNDate ()
16682 {
16683     time_t clock;
16684     struct tm *tm;
16685     char buf[MSG_SIZ];
16686
16687     clock = time((time_t *)NULL);
16688     tm = localtime(&clock);
16689     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16690             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16691     return StrSave(buf);
16692 }
16693
16694
16695 char *
16696 PositionToFEN (int move, char *overrideCastling)
16697 {
16698     int i, j, fromX, fromY, toX, toY;
16699     int whiteToPlay;
16700     char buf[MSG_SIZ];
16701     char *p, *q;
16702     int emptycount;
16703     ChessSquare piece;
16704
16705     whiteToPlay = (gameMode == EditPosition) ?
16706       !blackPlaysFirst : (move % 2 == 0);
16707     p = buf;
16708
16709     /* Piece placement data */
16710     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16711         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16712         emptycount = 0;
16713         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16714             if (boards[move][i][j] == EmptySquare) {
16715                 emptycount++;
16716             } else { ChessSquare piece = boards[move][i][j];
16717                 if (emptycount > 0) {
16718                     if(emptycount<10) /* [HGM] can be >= 10 */
16719                         *p++ = '0' + emptycount;
16720                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16721                     emptycount = 0;
16722                 }
16723                 if(PieceToChar(piece) == '+') {
16724                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16725                     *p++ = '+';
16726                     piece = (ChessSquare)(DEMOTED piece);
16727                 }
16728                 *p++ = PieceToChar(piece);
16729                 if(p[-1] == '~') {
16730                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16731                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16732                     *p++ = '~';
16733                 }
16734             }
16735         }
16736         if (emptycount > 0) {
16737             if(emptycount<10) /* [HGM] can be >= 10 */
16738                 *p++ = '0' + emptycount;
16739             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16740             emptycount = 0;
16741         }
16742         *p++ = '/';
16743     }
16744     *(p - 1) = ' ';
16745
16746     /* [HGM] print Crazyhouse or Shogi holdings */
16747     if( gameInfo.holdingsWidth ) {
16748         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16749         q = p;
16750         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16751             piece = boards[move][i][BOARD_WIDTH-1];
16752             if( piece != EmptySquare )
16753               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16754                   *p++ = PieceToChar(piece);
16755         }
16756         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16757             piece = boards[move][BOARD_HEIGHT-i-1][0];
16758             if( piece != EmptySquare )
16759               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16760                   *p++ = PieceToChar(piece);
16761         }
16762
16763         if( q == p ) *p++ = '-';
16764         *p++ = ']';
16765         *p++ = ' ';
16766     }
16767
16768     /* Active color */
16769     *p++ = whiteToPlay ? 'w' : 'b';
16770     *p++ = ' ';
16771
16772   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16773     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16774   } else {
16775   if(nrCastlingRights) {
16776      q = p;
16777      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16778        /* [HGM] write directly from rights */
16779            if(boards[move][CASTLING][2] != NoRights &&
16780               boards[move][CASTLING][0] != NoRights   )
16781                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16782            if(boards[move][CASTLING][2] != NoRights &&
16783               boards[move][CASTLING][1] != NoRights   )
16784                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16785            if(boards[move][CASTLING][5] != NoRights &&
16786               boards[move][CASTLING][3] != NoRights   )
16787                 *p++ = boards[move][CASTLING][3] + AAA;
16788            if(boards[move][CASTLING][5] != NoRights &&
16789               boards[move][CASTLING][4] != NoRights   )
16790                 *p++ = boards[move][CASTLING][4] + AAA;
16791      } else {
16792
16793         /* [HGM] write true castling rights */
16794         if( nrCastlingRights == 6 ) {
16795             int q, k=0;
16796             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16797                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16798             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16799                  boards[move][CASTLING][2] != NoRights  );
16800             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16801                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16802                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16803                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16804                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16805             }
16806             if(q) *p++ = 'Q';
16807             k = 0;
16808             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16809                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16810             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16811                  boards[move][CASTLING][5] != NoRights  );
16812             if(gameInfo.variant == VariantSChess) {
16813                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16814                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16815                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16816                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16817             }
16818             if(q) *p++ = 'q';
16819         }
16820      }
16821      if (q == p) *p++ = '-'; /* No castling rights */
16822      *p++ = ' ';
16823   }
16824
16825   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16826      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16827     /* En passant target square */
16828     if (move > backwardMostMove) {
16829         fromX = moveList[move - 1][0] - AAA;
16830         fromY = moveList[move - 1][1] - ONE;
16831         toX = moveList[move - 1][2] - AAA;
16832         toY = moveList[move - 1][3] - ONE;
16833         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16834             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16835             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16836             fromX == toX) {
16837             /* 2-square pawn move just happened */
16838             *p++ = toX + AAA;
16839             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16840         } else {
16841             *p++ = '-';
16842         }
16843     } else if(move == backwardMostMove) {
16844         // [HGM] perhaps we should always do it like this, and forget the above?
16845         if((signed char)boards[move][EP_STATUS] >= 0) {
16846             *p++ = boards[move][EP_STATUS] + AAA;
16847             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16848         } else {
16849             *p++ = '-';
16850         }
16851     } else {
16852         *p++ = '-';
16853     }
16854     *p++ = ' ';
16855   }
16856   }
16857
16858     /* [HGM] find reversible plies */
16859     {   int i = 0, j=move;
16860
16861         if (appData.debugMode) { int k;
16862             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16863             for(k=backwardMostMove; k<=forwardMostMove; k++)
16864                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16865
16866         }
16867
16868         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16869         if( j == backwardMostMove ) i += initialRulePlies;
16870         sprintf(p, "%d ", i);
16871         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16872     }
16873     /* Fullmove number */
16874     sprintf(p, "%d", (move / 2) + 1);
16875
16876     return StrSave(buf);
16877 }
16878
16879 Boolean
16880 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16881 {
16882     int i, j;
16883     char *p, c;
16884     int emptycount, virgin[BOARD_FILES];
16885     ChessSquare piece;
16886
16887     p = fen;
16888
16889     /* [HGM] by default clear Crazyhouse holdings, if present */
16890     if(gameInfo.holdingsWidth) {
16891        for(i=0; i<BOARD_HEIGHT; i++) {
16892            board[i][0]             = EmptySquare; /* black holdings */
16893            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16894            board[i][1]             = (ChessSquare) 0; /* black counts */
16895            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16896        }
16897     }
16898
16899     /* Piece placement data */
16900     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16901         j = 0;
16902         for (;;) {
16903             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16904                 if (*p == '/') p++;
16905                 emptycount = gameInfo.boardWidth - j;
16906                 while (emptycount--)
16907                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16908                 break;
16909 #if(BOARD_FILES >= 10)
16910             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16911                 p++; emptycount=10;
16912                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16913                 while (emptycount--)
16914                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16915 #endif
16916             } else if (isdigit(*p)) {
16917                 emptycount = *p++ - '0';
16918                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16919                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16920                 while (emptycount--)
16921                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16922             } else if (*p == '+' || isalpha(*p)) {
16923                 if (j >= gameInfo.boardWidth) return FALSE;
16924                 if(*p=='+') {
16925                     piece = CharToPiece(*++p);
16926                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16927                     piece = (ChessSquare) (PROMOTED piece ); p++;
16928                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16929                 } else piece = CharToPiece(*p++);
16930
16931                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16932                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16933                     piece = (ChessSquare) (PROMOTED piece);
16934                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16935                     p++;
16936                 }
16937                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16938             } else {
16939                 return FALSE;
16940             }
16941         }
16942     }
16943     while (*p == '/' || *p == ' ') p++;
16944
16945     /* [HGM] look for Crazyhouse holdings here */
16946     while(*p==' ') p++;
16947     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16948         if(*p == '[') p++;
16949         if(*p == '-' ) p++; /* empty holdings */ else {
16950             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16951             /* if we would allow FEN reading to set board size, we would   */
16952             /* have to add holdings and shift the board read so far here   */
16953             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16954                 p++;
16955                 if((int) piece >= (int) BlackPawn ) {
16956                     i = (int)piece - (int)BlackPawn;
16957                     i = PieceToNumber((ChessSquare)i);
16958                     if( i >= gameInfo.holdingsSize ) return FALSE;
16959                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16960                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16961                 } else {
16962                     i = (int)piece - (int)WhitePawn;
16963                     i = PieceToNumber((ChessSquare)i);
16964                     if( i >= gameInfo.holdingsSize ) return FALSE;
16965                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16966                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16967                 }
16968             }
16969         }
16970         if(*p == ']') p++;
16971     }
16972
16973     while(*p == ' ') p++;
16974
16975     /* Active color */
16976     c = *p++;
16977     if(appData.colorNickNames) {
16978       if( c == appData.colorNickNames[0] ) c = 'w'; else
16979       if( c == appData.colorNickNames[1] ) c = 'b';
16980     }
16981     switch (c) {
16982       case 'w':
16983         *blackPlaysFirst = FALSE;
16984         break;
16985       case 'b':
16986         *blackPlaysFirst = TRUE;
16987         break;
16988       default:
16989         return FALSE;
16990     }
16991
16992     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16993     /* return the extra info in global variiables             */
16994
16995     /* set defaults in case FEN is incomplete */
16996     board[EP_STATUS] = EP_UNKNOWN;
16997     for(i=0; i<nrCastlingRights; i++ ) {
16998         board[CASTLING][i] =
16999             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17000     }   /* assume possible unless obviously impossible */
17001     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17002     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17003     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17004                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17005     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17006     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17007     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17008                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17009     FENrulePlies = 0;
17010
17011     while(*p==' ') p++;
17012     if(nrCastlingRights) {
17013       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17014       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17015           /* castling indicator present, so default becomes no castlings */
17016           for(i=0; i<nrCastlingRights; i++ ) {
17017                  board[CASTLING][i] = NoRights;
17018           }
17019       }
17020       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17021              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17022              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17023              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17024         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17025
17026         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17027             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17028             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17029         }
17030         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17031             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17032         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17033                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17034         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17035                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17036         switch(c) {
17037           case'K':
17038               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17039               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17040               board[CASTLING][2] = whiteKingFile;
17041               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17042               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17043               break;
17044           case'Q':
17045               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17046               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17047               board[CASTLING][2] = whiteKingFile;
17048               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17049               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17050               break;
17051           case'k':
17052               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17053               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17054               board[CASTLING][5] = blackKingFile;
17055               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17056               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17057               break;
17058           case'q':
17059               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17060               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17061               board[CASTLING][5] = blackKingFile;
17062               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17063               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17064           case '-':
17065               break;
17066           default: /* FRC castlings */
17067               if(c >= 'a') { /* black rights */
17068                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17069                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17070                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17071                   if(i == BOARD_RGHT) break;
17072                   board[CASTLING][5] = i;
17073                   c -= AAA;
17074                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17075                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17076                   if(c > i)
17077                       board[CASTLING][3] = c;
17078                   else
17079                       board[CASTLING][4] = c;
17080               } else { /* white rights */
17081                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17082                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17083                     if(board[0][i] == WhiteKing) break;
17084                   if(i == BOARD_RGHT) break;
17085                   board[CASTLING][2] = i;
17086                   c -= AAA - 'a' + 'A';
17087                   if(board[0][c] >= WhiteKing) break;
17088                   if(c > i)
17089                       board[CASTLING][0] = c;
17090                   else
17091                       board[CASTLING][1] = c;
17092               }
17093         }
17094       }
17095       for(i=0; i<nrCastlingRights; i++)
17096         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17097       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17098     if (appData.debugMode) {
17099         fprintf(debugFP, "FEN castling rights:");
17100         for(i=0; i<nrCastlingRights; i++)
17101         fprintf(debugFP, " %d", board[CASTLING][i]);
17102         fprintf(debugFP, "\n");
17103     }
17104
17105       while(*p==' ') p++;
17106     }
17107
17108     /* read e.p. field in games that know e.p. capture */
17109     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17110        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17111       if(*p=='-') {
17112         p++; board[EP_STATUS] = EP_NONE;
17113       } else {
17114          char c = *p++ - AAA;
17115
17116          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17117          if(*p >= '0' && *p <='9') p++;
17118          board[EP_STATUS] = c;
17119       }
17120     }
17121
17122
17123     if(sscanf(p, "%d", &i) == 1) {
17124         FENrulePlies = i; /* 50-move ply counter */
17125         /* (The move number is still ignored)    */
17126     }
17127
17128     return TRUE;
17129 }
17130
17131 void
17132 EditPositionPasteFEN (char *fen)
17133 {
17134   if (fen != NULL) {
17135     Board initial_position;
17136
17137     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17138       DisplayError(_("Bad FEN position in clipboard"), 0);
17139       return ;
17140     } else {
17141       int savedBlackPlaysFirst = blackPlaysFirst;
17142       EditPositionEvent();
17143       blackPlaysFirst = savedBlackPlaysFirst;
17144       CopyBoard(boards[0], initial_position);
17145       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17146       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17147       DisplayBothClocks();
17148       DrawPosition(FALSE, boards[currentMove]);
17149     }
17150   }
17151 }
17152
17153 static char cseq[12] = "\\   ";
17154
17155 Boolean
17156 set_cont_sequence (char *new_seq)
17157 {
17158     int len;
17159     Boolean ret;
17160
17161     // handle bad attempts to set the sequence
17162         if (!new_seq)
17163                 return 0; // acceptable error - no debug
17164
17165     len = strlen(new_seq);
17166     ret = (len > 0) && (len < sizeof(cseq));
17167     if (ret)
17168       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17169     else if (appData.debugMode)
17170       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17171     return ret;
17172 }
17173
17174 /*
17175     reformat a source message so words don't cross the width boundary.  internal
17176     newlines are not removed.  returns the wrapped size (no null character unless
17177     included in source message).  If dest is NULL, only calculate the size required
17178     for the dest buffer.  lp argument indicats line position upon entry, and it's
17179     passed back upon exit.
17180 */
17181 int
17182 wrap (char *dest, char *src, int count, int width, int *lp)
17183 {
17184     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17185
17186     cseq_len = strlen(cseq);
17187     old_line = line = *lp;
17188     ansi = len = clen = 0;
17189
17190     for (i=0; i < count; i++)
17191     {
17192         if (src[i] == '\033')
17193             ansi = 1;
17194
17195         // if we hit the width, back up
17196         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17197         {
17198             // store i & len in case the word is too long
17199             old_i = i, old_len = len;
17200
17201             // find the end of the last word
17202             while (i && src[i] != ' ' && src[i] != '\n')
17203             {
17204                 i--;
17205                 len--;
17206             }
17207
17208             // word too long?  restore i & len before splitting it
17209             if ((old_i-i+clen) >= width)
17210             {
17211                 i = old_i;
17212                 len = old_len;
17213             }
17214
17215             // extra space?
17216             if (i && src[i-1] == ' ')
17217                 len--;
17218
17219             if (src[i] != ' ' && src[i] != '\n')
17220             {
17221                 i--;
17222                 if (len)
17223                     len--;
17224             }
17225
17226             // now append the newline and continuation sequence
17227             if (dest)
17228                 dest[len] = '\n';
17229             len++;
17230             if (dest)
17231                 strncpy(dest+len, cseq, cseq_len);
17232             len += cseq_len;
17233             line = cseq_len;
17234             clen = cseq_len;
17235             continue;
17236         }
17237
17238         if (dest)
17239             dest[len] = src[i];
17240         len++;
17241         if (!ansi)
17242             line++;
17243         if (src[i] == '\n')
17244             line = 0;
17245         if (src[i] == 'm')
17246             ansi = 0;
17247     }
17248     if (dest && appData.debugMode)
17249     {
17250         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17251             count, width, line, len, *lp);
17252         show_bytes(debugFP, src, count);
17253         fprintf(debugFP, "\ndest: ");
17254         show_bytes(debugFP, dest, len);
17255         fprintf(debugFP, "\n");
17256     }
17257     *lp = dest ? line : old_line;
17258
17259     return len;
17260 }
17261
17262 // [HGM] vari: routines for shelving variations
17263 Boolean modeRestore = FALSE;
17264
17265 void
17266 PushInner (int firstMove, int lastMove)
17267 {
17268         int i, j, nrMoves = lastMove - firstMove;
17269
17270         // push current tail of game on stack
17271         savedResult[storedGames] = gameInfo.result;
17272         savedDetails[storedGames] = gameInfo.resultDetails;
17273         gameInfo.resultDetails = NULL;
17274         savedFirst[storedGames] = firstMove;
17275         savedLast [storedGames] = lastMove;
17276         savedFramePtr[storedGames] = framePtr;
17277         framePtr -= nrMoves; // reserve space for the boards
17278         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17279             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17280             for(j=0; j<MOVE_LEN; j++)
17281                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17282             for(j=0; j<2*MOVE_LEN; j++)
17283                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17284             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17285             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17286             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17287             pvInfoList[firstMove+i-1].depth = 0;
17288             commentList[framePtr+i] = commentList[firstMove+i];
17289             commentList[firstMove+i] = NULL;
17290         }
17291
17292         storedGames++;
17293         forwardMostMove = firstMove; // truncate game so we can start variation
17294 }
17295
17296 void
17297 PushTail (int firstMove, int lastMove)
17298 {
17299         if(appData.icsActive) { // only in local mode
17300                 forwardMostMove = currentMove; // mimic old ICS behavior
17301                 return;
17302         }
17303         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17304
17305         PushInner(firstMove, lastMove);
17306         if(storedGames == 1) GreyRevert(FALSE);
17307         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17308 }
17309
17310 void
17311 PopInner (Boolean annotate)
17312 {
17313         int i, j, nrMoves;
17314         char buf[8000], moveBuf[20];
17315
17316         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17317         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17318         nrMoves = savedLast[storedGames] - currentMove;
17319         if(annotate) {
17320                 int cnt = 10;
17321                 if(!WhiteOnMove(currentMove))
17322                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17323                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17324                 for(i=currentMove; i<forwardMostMove; i++) {
17325                         if(WhiteOnMove(i))
17326                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17327                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17328                         strcat(buf, moveBuf);
17329                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17330                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17331                 }
17332                 strcat(buf, ")");
17333         }
17334         for(i=1; i<=nrMoves; i++) { // copy last variation back
17335             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17336             for(j=0; j<MOVE_LEN; j++)
17337                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17338             for(j=0; j<2*MOVE_LEN; j++)
17339                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17340             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17341             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17342             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17343             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17344             commentList[currentMove+i] = commentList[framePtr+i];
17345             commentList[framePtr+i] = NULL;
17346         }
17347         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17348         framePtr = savedFramePtr[storedGames];
17349         gameInfo.result = savedResult[storedGames];
17350         if(gameInfo.resultDetails != NULL) {
17351             free(gameInfo.resultDetails);
17352       }
17353         gameInfo.resultDetails = savedDetails[storedGames];
17354         forwardMostMove = currentMove + nrMoves;
17355 }
17356
17357 Boolean
17358 PopTail (Boolean annotate)
17359 {
17360         if(appData.icsActive) return FALSE; // only in local mode
17361         if(!storedGames) return FALSE; // sanity
17362         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17363
17364         PopInner(annotate);
17365         if(currentMove < forwardMostMove) ForwardEvent(); else
17366         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17367
17368         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17369         return TRUE;
17370 }
17371
17372 void
17373 CleanupTail ()
17374 {       // remove all shelved variations
17375         int i;
17376         for(i=0; i<storedGames; i++) {
17377             if(savedDetails[i])
17378                 free(savedDetails[i]);
17379             savedDetails[i] = NULL;
17380         }
17381         for(i=framePtr; i<MAX_MOVES; i++) {
17382                 if(commentList[i]) free(commentList[i]);
17383                 commentList[i] = NULL;
17384         }
17385         framePtr = MAX_MOVES-1;
17386         storedGames = 0;
17387 }
17388
17389 void
17390 LoadVariation (int index, char *text)
17391 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17392         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17393         int level = 0, move;
17394
17395         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17396         // first find outermost bracketing variation
17397         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17398             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17399                 if(*p == '{') wait = '}'; else
17400                 if(*p == '[') wait = ']'; else
17401                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17402                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17403             }
17404             if(*p == wait) wait = NULLCHAR; // closing ]} found
17405             p++;
17406         }
17407         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17408         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17409         end[1] = NULLCHAR; // clip off comment beyond variation
17410         ToNrEvent(currentMove-1);
17411         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17412         // kludge: use ParsePV() to append variation to game
17413         move = currentMove;
17414         ParsePV(start, TRUE, TRUE);
17415         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17416         ClearPremoveHighlights();
17417         CommentPopDown();
17418         ToNrEvent(currentMove+1);
17419 }
17420